Compare commits
2 Commits
ba5d262c6c
...
ea6a3ec8a5
| Author | SHA1 | Date | |
|---|---|---|---|
| ea6a3ec8a5 | |||
| 3c1061fbd8 |
@@ -0,0 +1,71 @@
|
||||
# ADR 0005 — Mantener el `.git` del repo padre ligero: no trackear artefactos hijos, purgar basura del historial, submódulos shallow
|
||||
|
||||
- **Fecha:** 2026-06-03
|
||||
- **Estado:** accepted
|
||||
|
||||
## Contexto
|
||||
|
||||
El `.git` del repo padre `fn_registry` había crecido a **475 MB**, un tamaño que ralentiza clones, `fn sync` y la operación diaria entre los tres PCs del ecosistema (`aurgi-pc`, `home-wsl`, `lucas-linux`). El diagnóstico identificó tres causas independientes, todas evitables:
|
||||
|
||||
1. **Artefactos hijos forzados al índice.** Pese a que el `.gitignore` ya tiene las reglas correctas (`apps/*/`, `analysis/*/`, `projects/*/`), un `.gitignore` no des-trackea archivos que ya estaban en el índice. Dos apps tenían contenido forzado: `apps/dag_engine/` (31 archivos: código Go + frontend + `app.md` + `README.md`) y `apps/shaders_lab/` (`app.md` + un binario `shaders_lab.exe`). El commit `d8db05e9` ("chore(dag_engine): app.md ... metadata trackeada por el padre") había trackeado dag_engine deliberadamente; esta decisión queda **anulada**. La convención correcta ya estaba demostrada por `projects/*/apps` (p.ej. `registry_dashboard`, `call_monitor`), que el padre no versiona en absoluto y funcionan bien.
|
||||
|
||||
2. **Basura en el historial.** Versiones antiguas de directorios que nunca debieron versionarse seguían viviendo en los commits pasados: `frontend/node_modules` (168 MB de binarios), `build/` raíz (54 MB de artefactos de compilación C++, 2299 archivos), `registry.db` (29 MB en ~7 versiones; regenerable con `fn index`) y `apps/shaders_lab/shaders_lab.exe` (~190 MB acumulados en ~10 versiones). En total ~440 MB de blobs muertos.
|
||||
|
||||
3. **Submódulos C++ con historia completa.** `cpp/vendor/{imgui,implot,implot3d,tracy,glfw,sdl3}` + `emsdk` son submódulos git legítimos (deps necesarias para compilar las apps imgui). Cada uno clonaba **toda la historia upstream**: `.git/modules` pesaba 338 MB para servir un working tree de 118 MB. imgui solo: 129 MB de `.git` (11.552 commits) para 8.9 MB de headers; sdl3: 146 MB (21.539 commits) para 55 MB de código. El proyecto compila contra **un único commit pinneado** por submódulo — el resto es historia ajena que nadie consulta.
|
||||
|
||||
## Decisión
|
||||
|
||||
Mantener el `.git` del padre ligero con tres medidas:
|
||||
|
||||
1. **El repo padre NO versiona el contenido de los artefactos hijos.** Cada app/analysis/project-app es un sub-repo Gitea independiente con su propio `.git` (ADR 0002); el padre solo conserva su metadata en `registry.db` (regenerable con `fn index`, que lee los artefactos del disco). Se sacan del índice con `git rm -r --cached` (con `--cached` SIEMPRE — sin él se borraría el working tree de los sub-repos). Único contenido versionado bajo `apps/` y `analysis/`: los marcadores `.gitkeep`. Bajo `projects/`: solo los `project.md`.
|
||||
|
||||
2. **El historial pasado se purga de basura con `git filter-repo`.** Se eliminan los blobs de `frontend/node_modules`, `build/` (raíz), `registry.db` y `apps/shaders_lab/shaders_lab.exe` de todos los commits. Esto reescribe la historia (cambian los SHAs) y requiere `git push --force`. Se añade `build/` (raíz) al `.gitignore` para evitar reincidencia (`node_modules`, `*.exe` y `registry.db` ya estaban).
|
||||
|
||||
3. **Los submódulos C++ se configuran shallow (`depth 1`).** Cada submódulo descarga solo el commit pinneado, no la historia upstream. Se marca `shallow = true` en `.gitmodules` para que los clones futuros nazcan shallow. El working tree mantiene el snapshot completo de cada dependencia, así que la compilación C++ no cambia.
|
||||
|
||||
## Cómo se ejecutó (2026-06-03)
|
||||
|
||||
```bash
|
||||
# 1. Untrack del índice (los archivos quedan en disco; los .git de los sub-repos conservan el código)
|
||||
git rm -r --cached apps/dag_engine apps/shaders_lab
|
||||
git commit -m "chore: untrack contenido de artefactos hijos (dag_engine, shaders_lab)"
|
||||
|
||||
# 2. Purga del historial (con git-filter-repo, descargado standalone)
|
||||
python3 git-filter-repo --strip-blobs-bigger-than 10M --force
|
||||
python3 git-filter-repo --invert-paths --path frontend/node_modules --path build \
|
||||
--path registry.db --path apps/dag_engine --path apps/shaders_lab --force
|
||||
git remote add origin <url> # filter-repo elimina el remote por seguridad
|
||||
git push --force origin master
|
||||
|
||||
# 3. Submódulos shallow (deinit + borrar el .git/modules full + re-clone --depth 1)
|
||||
for sm in cpp/vendor/{sdl3,imgui,tracy,glfw,implot,implot3d} emsdk; do
|
||||
git submodule deinit -f "$sm"
|
||||
rm -rf ".git/modules/$sm" # clave: deinit NO borra .git/modules
|
||||
git -c "submodule.$sm.shallow=true" submodule update --init --depth 1 "$sm"
|
||||
done
|
||||
sed -i '/^\turl = /a\\tshallow = true' .gitmodules
|
||||
git commit -m "chore: submodulos C++ en modo shallow (depth 1)" && git push origin master
|
||||
```
|
||||
|
||||
Resultado: `.git` **475 MB → 51 MB** (−89%). Desglose: `.git/objects` 137 MB → 16 MB (historial del registry limpio); `.git/modules` 338 MB → 35 MB (submódulos shallow). `cpp/vendor` en disco intacto (118 MB). `cmake configure` de `cpp/` OK con las deps shallow. Backup completo del `.git` pre-purga en `~/backups/fn_registry_purge_20260603/`.
|
||||
|
||||
## Alternativas descartadas
|
||||
|
||||
- **Solo `git rm --cached` sin purgar el historial.** Detiene el crecimiento futuro pero deja los ~440 MB de basura en los commits pasados. No reduce el `.git`. Insuficiente para el objetivo.
|
||||
- **Purgar solo `shaders_lab.exe`.** Mismo coste (force-push + re-clone en otros PCs) por mucha menos ganancia: deja `node_modules`, `build/` y `registry.db` en el historial.
|
||||
- **Borrar o purgar los submódulos C++.** Son deps legítimas necesarias para compilar las apps imgui. Purgarlas rompería la compilación. La vía correcta es shallow, no eliminación.
|
||||
- **`git filter-branch` en vez de `git-filter-repo`.** Más lento, deja `refs/original` y es propenso a errores. `filter-repo` es la herramienta recomendada por el propio git.
|
||||
|
||||
## Consecuencias
|
||||
|
||||
- **Force-push reescribe la historia del padre.** Los otros PCs (`aurgi-pc`, `home-wsl`) quedan con historia divergente y deben re-sincronizar: `git fetch origin && git reset --hard origin/master && git submodule update --init --recursive`. Trabajo local del padre sin pushear se pierde — verificar antes. Esta es la única parte irreversible y outward-facing; requiere confirmación humana explícita.
|
||||
- **Shallow es local y reversible.** No toca el repo padre ni los gitlinks; solo adelgaza el `.git` interno de cada submódulo. Reversible por dep con `git fetch --unshallow`. El `.gitmodules` con `shallow = true` hace que los clones frescos nazcan ligeros; los clones existentes deben re-aplicar el `deinit + rm .git/modules/<x> + update --depth 1` o re-clonar.
|
||||
- **Bumpear una dep shallow cuesta un `git fetch --depth 1 <commit>` extra** antes del checkout, porque el commit nuevo no está en el clon mínimo. Es fricción, no bloqueo.
|
||||
- **Se pierde `git log/blame/bisect` dentro de los submódulos** (la historia de SDL3/imgui), algo que casi nunca se hace en deps vendored.
|
||||
- **Operación repetible.** Si el `.git` vuelve a crecer por basura, el procedimiento de este ADR (untrack + filter-repo + shallow) es el runbook.
|
||||
|
||||
## Relación con otras reglas y ADRs
|
||||
|
||||
- [ADR 0002](0002-apps-analyses-as-dataforge-master.md) — apps/analyses como sub-repos `dataforge/<name>`. Este ADR refuerza su corolario: el padre no versiona su contenido.
|
||||
- `.claude/rules/apps_subrepo.md` — gotcha de pérdida de código en worktrees; misma raíz (artefactos = sub-repos independientes).
|
||||
- `.claude/rules/db_locations.md` — `registry.db` solo en la raíz y regenerable; por eso es purgable del historial.
|
||||
@@ -62,3 +62,4 @@ Qué se aprendió después. Útil cuando un ADR se supersede.
|
||||
| [0002](0002-apps-analyses-as-dataforge-master.md) | Apps y analyses como sub-repos `dataforge/<name>` con branch master | accepted |
|
||||
| [0003](0003-orphan-tu-as-separate-function-entry.md) | TU adicional de un parent function como entrada propia | accepted |
|
||||
| [0004](0004-telemetry-driven-capability-growth.md) | Telemetria de ejecuciones de Claude como motor de crecimiento del registry | accepted |
|
||||
| [0005](0005-keep-parent-git-lean.md) | Mantener el `.git` del padre ligero: no trackear artefactos hijos, purgar historial, submódulos shallow | accepted |
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# 2026-06-03
|
||||
|
||||
## 00:54 — Limpieza del repo padre + dag_engine v0.3.0
|
||||
|
||||
- Hecho: dag_engine v0.3.0 — acciones por DAG (pausar via tabla `dag_state`, eliminar con recarga del scheduler), vista Timeline (ejecuciones con barra de duracion), buscador de DAGs, tooltip de cron estilo crontab.guru (funcion nueva `describe_cron_expr_go_core` + endpoint `/api/cron/describe`), formato de fecha europeo DD/MM/AAAA, y fix del mismatch de casing snake_case entre el store y el frontend. Pusheado al sub-repo (ef91c3d). Daemon systemd-user sirviendo React + API en :4200.
|
||||
- Hecho: DAG de prueba `hola-mundo` (echo "hola mundo"), validado y ejecutado OK.
|
||||
- Hecho: Limpieza del `.git` del repo padre — ver ADR [0005](../adr/0005-keep-parent-git-lean.md). Tres medidas: (1) untrack de artefactos hijos forzados al indice (`apps/dag_engine` 31 archivos + `apps/shaders_lab` incl. .exe) con `git rm --cached`, revierte el commit d8db05e9; (2) purga del historial con `git-filter-repo` (frontend/node_modules 168MB, build/ 54MB, registry.db regenerable 29MB, shaders_lab.exe ~190MB en ~10 versiones); (3) submodulos C++ de `cpp/vendor` en modo shallow (depth 1), `.gitmodules` marcado `shallow = true`.
|
||||
- Resultado: `.git` 475MB -> 51MB (-89%). Codigo intacto (cpp/vendor 118MB en disco), `cmake configure` de cpp/ OK. Commits c5fb6ca (force-push tras purga) + ba5d262 (shallow). Backup completo en `~/backups/fn_registry_purge_20260603/`.
|
||||
- Pendiente: los otros 2 PCs (aurgi-pc, home-wsl) deben re-sincronizar tras el force-push: `git fetch origin && git reset --hard origin/master && git submodule update --init --recursive`. Trabajo local del padre sin pushear se perderia — verificar antes. Para que sus submodulos tambien adelgacen, re-clonar fresco o re-aplicar el shallow por submodulo.
|
||||
@@ -4,6 +4,11 @@ package core
|
||||
type DagContinueOn struct {
|
||||
Failure bool
|
||||
Skipped bool
|
||||
// ExitCodes lists exit codes that are tolerated: if the step exits with one
|
||||
// of these non-zero codes, it is treated as a non-failure (the DAG keeps
|
||||
// going and the run is not marked failed). The real exit code is still
|
||||
// recorded. Empty means only exit 0 is a success (unless Failure is set).
|
||||
ExitCodes []int
|
||||
}
|
||||
|
||||
// DagRetryPolicy configures automatic retries for a step.
|
||||
|
||||
@@ -29,8 +29,9 @@ type rawDagStep struct {
|
||||
}
|
||||
|
||||
type rawDagContinueOn struct {
|
||||
Failure bool `yaml:"failure"`
|
||||
Skipped bool `yaml:"skipped"`
|
||||
Failure bool `yaml:"failure"`
|
||||
Skipped bool `yaml:"skipped"`
|
||||
ExitCode []int `yaml:"exit_code"`
|
||||
}
|
||||
|
||||
type rawDagRetryPolicy struct {
|
||||
@@ -203,8 +204,9 @@ func normalizeStep(rs rawDagStep) (DagStep, error) {
|
||||
Depends: rs.Depends,
|
||||
Env: stepEnv,
|
||||
ContinueOn: DagContinueOn{
|
||||
Failure: rs.ContinueOn.Failure,
|
||||
Skipped: rs.ContinueOn.Skipped,
|
||||
Failure: rs.ContinueOn.Failure,
|
||||
Skipped: rs.ContinueOn.Skipped,
|
||||
ExitCodes: rs.ContinueOn.ExitCode,
|
||||
},
|
||||
RetryPolicy: DagRetryPolicy{
|
||||
Limit: rs.RetryPolicy.Limit,
|
||||
|
||||
@@ -3,11 +3,11 @@ name: dag_parse
|
||||
kind: function
|
||||
lang: go
|
||||
domain: core
|
||||
version: "1.0.0"
|
||||
version: "1.1.0"
|
||||
purity: pure
|
||||
signature: "func DagParse(data []byte) (DagDefinition, error)"
|
||||
description: "Parsea YAML de definicion de DAG en formato compatible con Dagu. Soporta schedule como string o lista, env como lista de maps single-key (formato Dagu), handler_on y handlers como aliases, steps con command/script/depends/continue_on, y type graph."
|
||||
tags: [dag, yaml, parsing, workflow, dagu, pure]
|
||||
description: "Parsea YAML de definicion de DAG en formato compatible con Dagu. Soporta schedule como string o lista, env como lista de maps single-key (formato Dagu), handler_on y handlers como aliases, steps con command/script/depends/continue_on (failure, skipped y exit_code) y retry_policy (limit, interval_sec), y type graph."
|
||||
tags: [dag, yaml, parsing, workflow, dagu, pure, scheduler]
|
||||
uses_functions: []
|
||||
uses_types: [dag_definition_go_core, dag_step_go_core, dag_handlers_go_core]
|
||||
returns: []
|
||||
@@ -25,6 +25,7 @@ tests:
|
||||
- "parsea env en formato lista de maps"
|
||||
- "parsea handler_on y handlers como alias"
|
||||
- "parsea continue_on y working_dir a nivel step"
|
||||
- "parsea continue_on.exit_code y retry_policy"
|
||||
- "parsea type graph"
|
||||
test_file_path: "functions/core/dag_parse_test.go"
|
||||
file_path: "functions/core/dag_parse.go"
|
||||
@@ -49,6 +50,31 @@ dag, err := DagParse(data)
|
||||
// dag.Steps[1].Depends = ["hello"]
|
||||
```
|
||||
|
||||
Step con tolerancia de fallos y reintentos:
|
||||
|
||||
```go
|
||||
data := []byte(`
|
||||
name: backup
|
||||
steps:
|
||||
- name: snapshot
|
||||
command: ./backup.sh
|
||||
continue_on:
|
||||
exit_code: [4] # exit 4 = "ok con avisos", no rompe el DAG
|
||||
retry_policy:
|
||||
limit: 3 # hasta 3 reintentos
|
||||
interval_sec: 10 # 10s entre intentos
|
||||
`)
|
||||
dag, _ := DagParse(data)
|
||||
// dag.Steps[0].ContinueOn.ExitCodes = [4]
|
||||
// dag.Steps[0].RetryPolicy.Limit = 3
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Funcion pura (el YAML es inmutable, no hay I/O). Internamente usa un struct rawDag para deserializar loosely y luego normaliza campos polimorficos. La estrategia de normalizacion: schedule string->[]string, env lista->map, handlers single-o-lista->[]DagStep. handler_on tiene precedencia sobre handlers si ambos estan presentes.
|
||||
|
||||
`continue_on` y `retry_policy` son declarativos: `DagParse` solo los normaliza al modelo `DagDefinition`. Quien los interpreta (reintentar, tolerar exit codes) es el executor que consuma el DAG — en este ecosistema, `apps/dag_engine`.
|
||||
|
||||
## Capability growth log
|
||||
|
||||
- v1.1.0 (2026-06-03) — minor: parsea `continue_on.exit_code` (lista de codigos de salida tolerados) y expone `retry_policy` ya existente en el modelo. Habilita reintentos y tolerancia de fallos reales en el executor de `dag_engine`.
|
||||
|
||||
@@ -187,6 +187,37 @@ steps:
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("parsea continue_on.exit_code y retry_policy", func(t *testing.T) {
|
||||
data := []byte(`
|
||||
name: dag-retry
|
||||
steps:
|
||||
- name: backup
|
||||
command: ./backup.sh
|
||||
continue_on:
|
||||
exit_code: [4, 5]
|
||||
retry_policy:
|
||||
limit: 3
|
||||
interval_sec: 10
|
||||
`)
|
||||
dag, err := DagParse(data)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(dag.Steps) != 1 {
|
||||
t.Fatalf("Steps: got %d, want 1", len(dag.Steps))
|
||||
}
|
||||
step := dag.Steps[0]
|
||||
if len(step.ContinueOn.ExitCodes) != 2 || step.ContinueOn.ExitCodes[0] != 4 || step.ContinueOn.ExitCodes[1] != 5 {
|
||||
t.Errorf("ContinueOn.ExitCodes: got %v, want [4 5]", step.ContinueOn.ExitCodes)
|
||||
}
|
||||
if step.RetryPolicy.Limit != 3 {
|
||||
t.Errorf("RetryPolicy.Limit: got %d, want 3", step.RetryPolicy.Limit)
|
||||
}
|
||||
if step.RetryPolicy.IntervalSec != 10 {
|
||||
t.Errorf("RetryPolicy.IntervalSec: got %d, want 10", step.RetryPolicy.IntervalSec)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("parsea step con function id y args", func(t *testing.T) {
|
||||
data := []byte(`
|
||||
name: dag-function
|
||||
|
||||
Reference in New Issue
Block a user