Files
sergio 550c98f275 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>
2026-05-19 14:48:34 +00:00

10 KiB

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-discernDiscernPipeline + discerners default. Reusado por yahweh-provider-fs y nouser-core::cluster.
  • crates/modules/shipote/shipote-coreWorkspaceManager + 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-clishipote (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/fsFileDataProvider ahora puebla mime_type con DiscernPipeline.
  • crates/modules/nouser/core/src/cluster.rspick_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.

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: MagicBytesCardProbeJsonProbeTomlProbeUtf8Probe. 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.