refactor(monorepo): reorganización lógica + renames + SDDs + split CHANGELOG
Reorganización física de crates/: - core/ (mezclaba 6 propósitos) se divide en protocol/, init/, runtime/, compat/ - shared/ (3 crates) se redistribuye en protocol/ e init/ - lapaloma (sub-módulo de ui_engine) se promueve a modules/pineal/ Renames de proyectos: - shipote → shuma (runtime de sandboxes) - nouser → akasha (explorador de Mónadas) - yahweh → nahual (motor GPUI, antes ui_engine/) - lapaloma → pineal (data-viz agnóstica) Fraccionamiento UI → core agnóstico: - vista-core (DeckState + snap, 175 LOC, 5 tests verdes) - barra-core (Task + render_html + sanitize, 90 LOC, 5 tests verdes) - vista-web y barra-web ahora son thin DOM bindings Documentación nueva: - 16 SDDs por subdirectorio (≤80 LOC c/u): protocol/init/runtime/compat + 10 módulos + apps/ - docs/STATUS.md con cifras reales por proyecto - docs/ROADMAP.md con plan a finalización (6 hitos, ~6-8 semanas) - CHANGELOG.md particionado en docs/changelog/<proyecto>.md (7 buckets) Automatización: - scripts/reorg.py — script idempotente que: git mv directorios, renombra package names, recomputa path = refs, reescribe imports rust, actualiza workspace Cargo.toml. Soporta --dry-run. - scripts/split-changelog.py — particiona CHANGELOG por componente. Validación: - cargo check --workspace pasa (124 crates + 2 nuevos cores). - 10 tests adicionales (5 en vista-core + 5 en barra-core) verdes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
# Arquitectura de shipote
|
||||
|
||||
## Diagrama de capas
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ Apps (binarios) │
|
||||
│ │
|
||||
│ shipote-daemon shipote (CLI) shipote-shell │
|
||||
│ crates/apps/... crates/apps/... crates/apps/... │
|
||||
└─────────┬─────────────────────┬──────────────────────┬───────────┘
|
||||
│ │ │
|
||||
│ postcard frames (length-prefixed)
|
||||
│ │ │
|
||||
└─────────────────────┴──────────────────────┘
|
||||
│
|
||||
shipote-protocol — wire types
|
||||
crates/modules/shipote/shipote-protocol
|
||||
│
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ shipote-core — runtime │
|
||||
│ crates/modules/shipote/shipote-core │
|
||||
│ │
|
||||
│ WorkspaceManager (Arc<Mutex<Inner>>) │
|
||||
│ ├ workspaces: HashMap<WorkspaceId, WorkspaceState> │
|
||||
│ ├ saved_pipelines, pipeline_flows, pipeline_supervisors │
|
||||
│ ├ restart_specs, pending_pipeline_restarts │
|
||||
│ └ dirty: AtomicBool (snapshot incremental) │
|
||||
│ │
|
||||
│ Modules: │
|
||||
│ - pipeline.rs (run_pipeline, splitter, merger, TokenBucket) │
|
||||
│ - flow_channel.rs (Unix socket + tokio::broadcast + replay) │
|
||||
│ - logbuf.rs (ring buffer 64 KiB para stdout/stderr) │
|
||||
│ - stats.rs (WorkspaceStats: RSS/CPU/peak/cores) │
|
||||
│ - persist.rs (ShipoteSnapshot v4 con JSON atomic write) │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌──────────────────────────────────────────────────────────────────┐
|
||||
│ Soporte compartido │
|
||||
│ │
|
||||
│ shipote-card shipote-discern │
|
||||
│ - WorkspaceSpec - DiscernPipeline │
|
||||
│ - PipelineSpec - MagicBytes, JsonProbe, etc. │
|
||||
│ - CommandRef - Detecta brahman:card │
|
||||
│ - DiscernPolicy │
|
||||
│ │
|
||||
│ ente-incarnate (crates/shared/) — extraído de ente-soma │
|
||||
│ - Incarnator { caps, env, stdio, pre_exec } │
|
||||
│ - CapabilitySet::detect (user_ns, cgroup, kernel version) │
|
||||
│ - ChildStdio, ChildSetup (NoNewPrivs, Chdir, ...) │
|
||||
│ - clone(2) + namespaces + cgroup move (post-clone setup) │
|
||||
└──────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
brahman-card · ente-incarnate · nix · libc
|
||||
```
|
||||
|
||||
## Crates (11 nuevos)
|
||||
|
||||
### Core del runtime
|
||||
- **`crates/shared/ente-incarnate`** — rutina extraída del Init para encarnar Cards en procesos aislados. Reusable por cualquier supervisor. NO requiere ser PID 1.
|
||||
- **`crates/modules/shipote/shipote-card`** — tipos: `WorkspaceSpec`, `PipelineSpec`, `CommandRef`, `FlowEdge`, `DiscernPolicy`, `QuotaEnforcement`. Compilan a `brahman_card::Card`.
|
||||
- **`crates/modules/shipote/shipote-protocol`** — wire postcard length-prefixed sobre Unix socket en `$XDG_RUNTIME_DIR/shipote.sock`.
|
||||
- **`crates/modules/shipote/shipote-discern`** — `DiscernPipeline` + discerners default. Reusado por `yahweh-provider-fs` y `nouser-core::cluster`.
|
||||
- **`crates/modules/shipote/shipote-core`** — `WorkspaceManager` + módulos `pipeline`, `flow_channel`, `logbuf`, `persist`, `stats`.
|
||||
|
||||
### Binarios
|
||||
- **`crates/apps/shipote-daemon`** — long-running. Escucha admin socket, ejecuta el dispatch, reaper periódico.
|
||||
- **`crates/apps/shipote-cli`** — `shipote` (clap). Para administración + scripting.
|
||||
- **`crates/apps/shipote-shell`** — GUI con `yahweh_launcher`. Dashboard: estado, workspaces, comandos, flows, quotas, sparkline.
|
||||
- **`crates/apps/shipote-gateway`** — HTTP/JSON adapter. `POST /rpc` con body JSON `Request` → traduce a postcard, round-trip al daemon, retorna JSON. Default `127.0.0.1:7378`.
|
||||
|
||||
### Consumidores del discerner (externos a shipote pero usándolo)
|
||||
- **`crates/modules/ui_engine/libs/providers/fs`** — `FileDataProvider` ahora puebla `mime_type` con `DiscernPipeline`.
|
||||
- **`crates/modules/nouser/core/src/cluster.rs`** — `pick_lens` usa el discerner como fallback cuando la extensión cae a `Lens::Grid`.
|
||||
|
||||
## Modelo conceptual
|
||||
|
||||
### Workspace
|
||||
Espacio aislado raíz. Una **Card con `kind: Ente`, `payload: Virtual`** que aloja N comandos hijos. Su `SomaSpec` define el sandbox compartido.
|
||||
|
||||
```toml
|
||||
label = "demo"
|
||||
on_exit = "reap"
|
||||
ttl = 60000 # opcional, ms
|
||||
|
||||
[soma.rlimits]
|
||||
mem_bytes = 10485760 # 10 MiB
|
||||
nproc = 4
|
||||
|
||||
[soma.cgroup]
|
||||
path = "shipote/demo"
|
||||
|
||||
[quota_enforce]
|
||||
mem = "kill" # None | Log | Kill
|
||||
```
|
||||
|
||||
### CommandRef
|
||||
Un comando dentro del workspace. Su `SomaSpec` se **intersecta** con el del padre (OR de namespaces, MIN de rlimits).
|
||||
|
||||
### PipelineSpec
|
||||
DAG de `CommandRef` con edges tipados. Soporta:
|
||||
- **1→1**: pipe directo (sin task intermedio).
|
||||
- **1→N (fan-out)**: splitter task replica.
|
||||
- **N→1 (fan-in)**: merger task con mpsc (una sub-task por reader → channel → escribir al consumer.stdin).
|
||||
|
||||
### Flow channel (data plane)
|
||||
Unix socket en `$XDG_RUNTIME_DIR/shipote-flow-<pipeline_id>-<edge_idx>.sock`. Cada subscriber recibe primero el replay (cap por chunks y/o bytes), después el broadcast live. Tokio `broadcast::Sender<Arc<Vec<u8>>>` — zero-copy entre subscribers.
|
||||
|
||||
### Discerner
|
||||
Pipeline ordenado: `MagicBytes` → `CardProbe` → `JsonProbe` → `TomlProbe` → `Utf8Probe`. Cada uno devuelve `Discernment { ty: TypeRef, confidence, mime, lens }`.
|
||||
|
||||
## Decisiones clave de arquitectura
|
||||
|
||||
### Por qué `ente-incarnate` es separado
|
||||
La rutina de aislamiento (clone+ns+cgroup+rlimits) vivía dentro del Init (`ente-soma`). Era global y atada a PID 1. La separamos en Fase 0 para que shipote (y futuros supervisores) puedan reusarla sin implicar privilegio.
|
||||
|
||||
Hoy `ente-soma` es un wrapper de ~30 líneas sobre `ente-incarnate` que preserva la semántica histórica (`set_bus_sock` global + `strict_caps=false`). `ente-zero` sigue funcionando intocado.
|
||||
|
||||
### Por qué pipeline-level restart relanza el pipeline ENTERO
|
||||
Los pipes intermedios de un comando que muere ya están cerrados. Restart parcial no funciona. La identidad ULID del pipeline cambia entre intentos; `restart_count` y `current_backoff_ms` se preservan en el supervisor.
|
||||
|
||||
### Por qué replay buffer guarda ANTES del broadcast
|
||||
Si guardas después, un subscriber que conecta entre `broadcast::send` y `replay.push_back` puede perder ese chunk. El orden importa: replay → broadcast.
|
||||
|
||||
### Por qué `pipe2(O_CLOEXEC)` siempre
|
||||
Sin `O_CLOEXEC`, el siguiente comando del pipeline hereda copias del write-end del pipe anterior. El read-end nunca ve EOF y el consumidor cuelga. Bug encontrado y arreglado en Fase F.
|
||||
|
||||
### Por qué AsyncFd + non-blocking en taps/mergers
|
||||
`tokio::fs::File::from_std` con un FD blocking bloquea el reactor entero. La solución: `fcntl(F_SETFL, O_NONBLOCK)` antes de envolver en `AsyncFd<OwnedFd>`. Patrón usado en splitter, merger y log drainer.
|
||||
|
||||
### Por qué token-bucket real reemplazó el sleep simple
|
||||
El sleep `chunk_size / rate` era uniforme y no permitía burst legítimo. `TokenBucket` con refill por wall time + `capacity=rate_bps` (1s burst) permite burst real y throttle suave.
|
||||
|
||||
### Por qué `dirty: AtomicBool` para snapshot incremental
|
||||
Cada SIGTERM hacía full re-serialize aunque no hubiera cambios. `Ordering::Relaxed` está bien porque el peor caso es un save innecesario. `restore_snapshot` resetea dirty=false (acabamos de hidratar, no es mutation).
|
||||
|
||||
### Por qué auth con SO_PEERCRED y `target: "audit"`
|
||||
SO_PEERCRED es automático en Unix sockets — leer `ucred.uid` del peer y comparar con el propio uid. Sin grupos, sin caps. `SHIPOTE_TRUST_ANYONE=1` es el escape hatch.
|
||||
|
||||
`target: "audit"` separa el log de mutaciones del log normal. Filtrable con `EnvFilter`: `SHIPOTE_LOG=warn,audit=info`.
|
||||
|
||||
## Versión de snapshot
|
||||
|
||||
| Versión | Campos | Forward-compat |
|
||||
|---------|--------|----------------|
|
||||
| v1 | `workspaces` | ✓ |
|
||||
| v2 | + `saved_pipelines` | ✓ (default vacío) |
|
||||
| v3 | + `live_pipelines` | ✓ (default vacío) |
|
||||
| v4 | + `stats_history` per workspace (cap 16) | ✓ (default vacío) |
|
||||
|
||||
`SNAPSHOT_VERSION = 4`. Reader rechaza versiones > 4 (forward-incompat). Todos los campos nuevos usan `#[serde(default)]`.
|
||||
|
||||
## Wire protocol
|
||||
|
||||
Todos los mensajes son enums tagged externamente, serializados con `postcard`. Framing: u32 BE length-prefix + payload. Max frame: 1 MiB.
|
||||
|
||||
### Requests
|
||||
- Reads: `Ping`, `Health`, `Capabilities`, `WorkspaceList`, `WorkspaceStats`, `WorkspaceStatsHistory`, `WorkspaceFullSummary`, `WorkspaceQuota`, `CommandList`, `CommandLogs`, `PipelineSavedList`, `FlowList`, `FlowThroughput`, `Discern`.
|
||||
- Mutaciones (loguean audit): `WorkspaceCreate`, `WorkspaceStop`, `Run`, `PipelineRun`, `PipelineRunSaved`, `PipelineStop`, `PipelineSave`, `PipelineDrop`, `FlowDrop`.
|
||||
Reference in New Issue
Block a user