2588673caf57bede06a28f7f2eb8c282533caf82
446 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2e6afd0973 |
feat(brahman-net+handshake): stop_providing automatico en cleanup
Cierra el pendiente conocido del DHT: hasta ahora cuando una sesion con outputs cerraba (Farewell, EOF, error), el record que la anunciaba en el DHT seguia vivo hasta su TTL natural (~24h en kad default). Consumers remotos podian descubrir un peer "vivo" que ya no servia nada. Cambios: - BrahmanNet::stop_providing(key) (nuevo): contraparte simetrica de start_providing. Manda Command::StopProviding al swarm que llama kad.stop_providing(&key). Borra el record local al instante; replicas remotas siguen expirando por TTL (kad no expone retraccion cross-peer, simetrico al hecho de que start_providing tambien propaga eventualmente). - brahman_handshake::network::withdraw_outputs(net, card) (nuevo): contraparte de announce_outputs. Itera card.flow.output y llama net.stop_providing(flow_dht_key(...)) por cada uno. - server::cleanup: extrae la ResolvedCard removida del registro de sesiones (en lugar de descartarla) y, si config.net esta set, llama withdraw_outputs(net, &card) antes de broadcast_match_diffs. Tests: nuevo E2E dht_discovery_withdraws_on_session_cleanup: 1. A registra Card con flow.output = monad-list:json. 2. B descubre a A via find_remote_providers (assert before contains a_peer). 3. Cliente local de A hace farewell -> cleanup -> withdraw_outputs. 4. Espera a que la sesion salga del registro + 100ms para que el swarm procese el Command. 5. Nueva query desde B: after NO debe contener a_peer. 3 tests verdes en network_discovery.rs (positivo, negativo, withdraw). 18 tests totales en handshake + net. |
||
|
|
fa0ed98880 |
feat(ente-zero): wire de Arje con brahman-net (red P2P opcional + keypair persistente)
Cierra el ultimo pendiente del plan de red: Arje ahora puede arrancar
opcionalmente con BrahmanNet configurado, persistir su identidad
libp2p entre reboots, y participar en la malla brahman como nodo
publico. Sin breaking changes: usuarios actuales (sin env vars) siguen
viendo el comportamiento Unix-only de antes.
Activacion por env vars:
- BRAHMAN_LISTEN_MULTIADDR — si set, activa la red P2P. Ej:
/ip4/0.0.0.0/tcp/4101 (publico), /ip4/127.0.0.1/tcp/0 (loopback).
- BRAHMAN_KEYPAIR_PATH — override del path donde se persiste la
keypair Ed25519. Defaults: PID 1 -> /var/lib/brahman/init-keypair.bin;
dev mode -> $XDG_DATA_HOME/brahman/init-keypair.bin con fallbacks.
- BRAHMAN_BOOTSTRAP_PEERS — multiaddrs coma-separados a dial-ear al
arranque para entrar al DHT.
Comportamiento al activarse:
1. keypair_store::load_or_generate carga keypair de disco o genera y
persiste una nueva (32 bytes raw, 0o600, atomic rename). peer_id
estable across reboots.
2. BrahmanNet::with_keypair arma el swarm con esa identidad.
3. net.listen() resuelve la addr y se loggea.
4. BRAHMAN_BOOTSTRAP_PEERS si set -> dial cada multiaddr.
5. ServerConfig.net = Some(net), activando announce_outputs automatico
en el DHT por cada Card con outputs.
6. Ademas del Unix accept loop, se monta libp2p accept loop sobre el
mismo Server compartido (Arc<Server>). Sesiones locales y remotas
conviven en las mismas tablas.
Refactor del Unix accept loop: antes consumia server via run().await;
ahora usa Arc<Server>::accept_one().await en loop para coexistir con
el libp2p accept loop sin moverse el server.
Degradacion gracil: si la keypair no carga, multiaddr invalido,
listen falla, bootstrap dial revienta -> log + continuamos en
Unix-only. Doctrina PID 1 ("ningun subsistema opcional rompe el
bucle primordial") preservada.
Tests: 4 unit en keypair_store: generate_persist_and_reload_yields_
same_peer_id (la property fundamental), rejects_corrupted_file,
persisted_file_is_owner_only (0o600 verificados), default_path_
honors_env.
Activacion en una linea:
BRAHMAN_LISTEN_MULTIADDR=/ip4/0.0.0.0/tcp/4101 ente-zero
Pendientes: stop_providing en cleanup, allowlist/denylist (PKI),
rotacion de keypair sin perder peer_id.
|
||
|
|
c164e9f422 |
feat(brahman-handshake): Fase 3 — trust remoto via firma Ed25519
Cuarto paso del plan "el encuentro entre Entes no se restringe a
local". Cierra la falla de seguridad que dejaba la red P2P abierta:
hasta ahora un atacante que pudiera dial-ar al multiaddr del Init
podia registrar cualquier Card con cualquier label/flow. Fase 3
exige que el Hello via libp2p venga firmado con la misma keypair
Ed25519 que produce el peer_id autenticado por Noise.
Modelo de trust:
- Local Unix: SO_PEERCRED del kernel autentica. Firma opcional pero
verificada si presente (defensa en profundidad).
- Remoto libp2p: firma obligatoria; public key del Hello debe derivar
al peer_id autenticado por Noise. Si falta o no coincide ->
HandshakeError::Unauthorized.
Wire:
- Hello.signature: Option<HelloSignature> (default None, backwards
compat para path Unix).
- HelloSignature { public_key: Vec<u8>, signature: Vec<u8> } —
public_key en formato canonico libp2p (encode_protobuf), firma
Ed25519 sobre (SIGNATURE_VERSION, WireCard, Option<WitInterface>)
serializado postcard.
Nuevo modulo signature:
- sign_hello / verify_hello con SignatureError tipado.
- 4 unit tests: roundtrip, peer mismatch, card tampered, sig flipped.
Server:
- Session<S> gana expected_peer: Option<PeerId>.
- session_from_libp2p_stream(stream, peer) para path remoto;
session_from_stream sin peer para Unix.
- do_handshake exige firma + verifica peer match si expected_peer.
Client:
- connect_with_stream_signed(stream, card, wit, &Keypair) (nuevo).
- connect_libp2p ahora requiere &Keypair (breaking change).
BrahmanNet:
- Almacena Arc<Keypair> internamente; expose keypair() accessor.
Truco: ed25519::Keypair SI es Clone, se duplica para que swarm
(Noise) y caller (signing) compartan identidad sin copiar bytes.
- with_keypair rechaza no-Ed25519.
Tests: 4 unit signature + 1 E2E negativo nuevo
(libp2p_handshake_rejects_mismatched_signing_key) + E2E positivo
ya pasaba con keypair correcta. 90+ tests verdes en
brahman-handshake/brahman-net/brahman-card/minga-p2p.
Lo que cierra: la cadena completa de discovery + connect + trust
funciona cross-machine sin paths hardcodeados ni confianza
implicita. Brahman-net es una malla publicamente dial-able CON
autenticacion criptografica end-to-end.
Pendientes futuros: stop_providing en cleanup, wire de Arje con
ServerConfig.net configurado, allowlist/denylist de peers,
persistencia de la keypair entre reboots.
|
||
|
|
2059af4fb9 |
feat(brahman-handshake): Fase 2 — discovery remoto via DHT por flow type
Tercer paso del plan "el encuentro entre Entes no se restringe a
local". Cuando un Init local acepta una sesion cuya Card declara
outputs, anuncia al DHT (Kademlia, via brahman-net) que el provee
esos flow types. Cualquier nodo conectado al mismo DHT puede
consultar y obtener la lista de PeerId's que sirven el flow.
API nueva en brahman_handshake::network:
- flow_dht_key(flow_name, type_ref) -> [u8; 32]: blake3 hash de
"brahman-flow|v1|{flow}|{type_canon}". Determinista cross-host.
Cambiar la canonicalizacion rompe compatibilidad — el prefijo v1
documenta version del esquema y obliga a bump al modificar.
- announce_outputs(net, card): start_providing por cada flow.output.
Idempotente, fire-and-forget.
- find_remote_providers(net, flow_name, type_ref) -> Vec<PeerId>:
query DHT. Lista vacia si nadie anuncia.
Wire en el server:
- ServerConfig gana pub net: Option<Arc<BrahmanNet>>. Si esta set,
cada Card registrada con outputs se anuncia automaticamente al DHT
desde register_session. None = server "ciego al DHT".
- Debug manual de ServerConfig (BrahmanNet no es Debug).
Canonicalizacion del TypeRef:
- Primitive { name } -> "prim:{name}"
- Wit { package, interface, name } -> "wit:{package}#{interface_or_empty}#{name}"
Tests: 2 nuevos en tests/network_discovery.rs:
- dht_discovery_finds_remote_provider: 2 nodos, A registra Card con
flow.output = monad-list:json, B dial-ea a A, B llama
find_remote_providers y descubre el peer_id de A.
- dht_discovery_negative_unknown_flow: B busca flow inexistente,
devuelve [] sin colgarse.
Callers actualizados con net: None: tests existentes + ente-zero
(arje aun no expone red; pasar Some(Arc<BrahmanNet>) cuando quiera
publicar al DHT remoto).
Lo que esto desbloquea: un nouser daemon en maquina A puede ser
descubierto por nouser-explorer en maquina B sin conocimiento previo
del peer — solo necesitan compartir DHT (via bootstrap inicial).
Pendiente para Fase 3: trust (firma Ed25519 en Cards remotas) +
stop_providing al cleanup de sesion.
|
||
|
|
73dadbb166 |
feat(brahman-handshake): Fase 1 — handshake brahman sobre stream libp2p
Segundo paso del plan "el encuentro entre Entes no se restringe a
local". El protocolo brahman (Hello / HelloAck / Ping / Pong /
MatchEvent / Farewell, frames postcard length-prefixed) ahora tambien
viaja sobre streams libp2p de la malla brahman-net — el mismo Init
acepta sesiones por Unix socket Y por libp2p indistintamente, y un
consumer remoto puede dial-ar al multiaddr y completar handshake.
Cambios:
- Session<S> y Client<S> genericos: ambos dejan de estar atados a
UnixStream y pasan a ser genericos sobre S: AsyncRead + AsyncWrite
+ Unpin + Send + 'static. El path Unix queda como
Client = Client<UnixStream> (default generico). Constructores
nuevos: Server::session_from_stream(stream),
Client::connect_with_stream(stream, card, wit).
- Refactor del post-handshake con split: tokio::select! sobre
&mut self.stream requeria S: Sync indirectamente, y libp2p::Stream
no es Sync. Reemplazado por tokio::io::split(stream) -> reader loop
principal + writer task separada que drena el push channel. Writer
compartido bajo Arc<Mutex<WriteHalf<S>>> para serializar Pong/Error
inline con los MatchEvents pusheados. Cleanup garantizado en todas
las ramas. La logica del post-handshake migra a funciones libres
(run_post_handshake, handle_inbound_frame, cleanup,
broadcast_match_diffs, do_handshake, register_session,
validate_hello).
- Nuevo modulo brahman-handshake::network:
- BRAHMAN_HANDSHAKE_PROTOCOL = "/brahman/handshake/1.0.0"
- LibP2pHandshakeStream = Compat<libp2p::Stream>
- run_libp2p_accept_loop(server, net): accept loop que delega cada
stream entrante a session_from_stream(stream.compat()). Sesiones
libp2p y Unix conviven en el mismo Server — comparten broker,
push table, last_matches.
- connect_libp2p(net, peer, card, wit): abre stream libp2p al peer
y arranca handshake.
- NetworkError tipado.
Deps: brahman-handshake gana brahman-net, futures, tokio-util.
brahman-net re-exporta Multiaddr, PeerId, Stream, StreamProtocol,
Protocol, OpenStreamError para que callers no necesiten dep directa
a libp2p.
Tests: 9 verdes en el path Unix (sin regresion). Nuevo
tests/network_libp2p.rs E2E que arma server con BrahmanNet, hace
listen TCP, monta accept loop; cliente con su propio BrahmanNet
dial-ea al peer_id, completa handshake remoto, ping, farewell.
Verifica que la sesion se registro durante la conversacion y se
removio tras farewell.
|
||
|
|
ad0d475a2e |
feat(brahman-net): capa P2P compartida — Fase 0 (extracción del swarm)
Primer paso del plan "el encuentro entre Entes no se restringe a local". El swarm libp2p que vivía dentro de minga-p2p::network (282 LOC) sale a una crate compartida brahman-net para que cualquier protocolo de la familia (handshake brahman remoto en Fase 1, sync minga, futuros) reuse una sola malla TCP+Noise+Yamux+Kad+Identify+Stream. BrahmanNet expone: - new() / with_keypair() para identidad efimera o persistente - API de comandos uniforme: dial, listen, add_dht_peer, find_closest_peers, start_providing, find_providers - Publica peer_id (libp2p) y control (stream::Control) — cada protocolo registra su StreamProtocol sin acoplarse al swarm - Re-exporta Stream y StreamProtocol para evitar dep directa a libp2p minga-p2p::network reduce de 282 LOC a 22: re-export del nuevo BrahmanNet bajo el alias historico LibP2pNode (zero churn en MingaPeer) y la const SYNC_PROTOCOL = "/minga/sync/1.0.0" especifica del sub-protocolo de sync Minga. Aclaracion semantica anclada por el usuario: Arje es el init (PID 1), Brahman es el encuentro entre Entes. El nombre brahman-net refleja que la malla pertenece al encuentro, no al runtime — Minga es un cliente de la malla, no su dueño. Tests: minga-p2p completo verde (58 tests, sin regresion). Behavior identico — solo se movio codigo, ningun cambio funcional. |
||
|
|
6f993f4268 |
refactor(explorer+card): independencia jerarquica enforced
Cierra el unico debt estructural detectado en el audit de independencia: nouser-explorer ya no arrastra nouser-core (que aportaba notify/walkdir/sled/blake3 al grafo de compilacion de una UI que solo habla JSON contra un socket). - Cliente movido: engine_socket::client::list_monads (~60 LOC, std + serde_json puros) emigra de nouser_core::engine_socket a nouser_card::query::client. Vive donde viven los wire types, consistente con el principio "un consumer importa el contrato, no el runtime del productor". - Drop dep: nouser-explorer deja de depender de nouser-core. Verificado con cargo tree: notify, sled, blake3 desaparecen del grafo del binario. - Fallback "falla hacia la simplicidad": nueva resolve_socket() en el explorer intenta primero broker discovery; si el broker no responde / no hay init vivo, fallback directo al default_socket_path. El explorer queda funcional contra un daemon huerfano (standalone sin init) — completa "consciente cuando hay ecosistema, soberano cuando esta solo". - socket_source gana tercer estado "default-path" para visibilidad. Audit estructural confirmo que el resto del ecosistema ya respeta el principio. Brahman es pegamento opcional, no chasis obligatorio — y ahora el grafo de Cargo lo enforcea, no solo la convencion. Tests: 4 + 10 + 27 verdes. Cliente movido ejercitado end-to-end por los 3 tests integracion de engine_socket. |
||
|
|
2ae888bc8f |
feat(explorer+daemon): discovery dinamico via broker + query socket
Cierra el "explorer encuentra al daemon de forma totalmente dinamica"
del meta-plan. La UI deja de hardcodear el socket admin: descubre al
daemon nouser via MatchEvent::Available del broker y le consulta sus
Monadas directo.
Pipeline end-to-end:
- Daemon publica engine Card con service_socket = $XDG_RUNTIME_DIR/
nouser-engine.sock y flow.output = monad-list:json.
- Daemon binda Unix socket en ese path con listener blocking que
sirve nouser_card::query::QueryRequest::ListMonads, responde
ListMonadsResponse { engine, monads: Vec<MonadView> }.
- Explorer construye consumer Card con flow.input matched,
brahman_sidecar::await_provider_blocking le devuelve el socket,
y nouser_core::engine_socket::client::list_monads lo consulta.
- Cachea el socket; cualquier fallo de query lo invalida y la
proxima iteracion re-descubre.
Wire types nuevos en nouser_card::query:
- QueryRequest::ListMonads
- ListMonadsResponse { engine: EngineInfo, monads: Vec<MonadView> }
- MonadView: proyeccion slim de MonadManifest SIN centroid ni
members (KB que no tienen por que viajar cada poll).
- transport::default_socket_path() con env override.
Listener en nouser_core::engine_socket: spawn_listener + client
blocking con QueryError tipado. 3 tests integracion verdes.
Refactor explorer:
- Drop dep brahman-admin, add brahman-sidecar/nouser-card/nouser-core.
- State: socket cache + snapshot + socket_source informativo.
- TickOutcome enum desacopla la I/O del UI.
Trade-offs: polling 2s (no streaming — broker no empuja Data cards
hoy), re-discovery full en error (discovery es barato).
Tests: 10 (nouser-card +3 query) + 27 (nouser-core +3 engine_socket)
+ 4 (sidecar) verdes. Explorer compila clean.
|
||
|
|
b23ddf2980 |
feat(nous-real): cache de embeddings + write-through al CAS de arje
Cierra el ciclo del feedback: el modelo real (fastembed-allMiniLML6V2, ~1-50ms por archivo) era invocado ciegamente en cada re-cluster del watcher. Ahora se cachea por sha256(bytes-vistos) + model_id, con write-through al CAS de arje. Pipeline en handle_file: 1. Lee primeros 8 KiB del archivo (igual que antes). 2. file_sha = ente_cas::sha256_of(buf) — hash de los bytes que el modelo *realmente* verá. Garantiza que un archivo creciendo mas alla de la ventana sin tocar la cabeza siga sirviendo cache hits. 3. Cache lookup -> HIT: respuesta en us, sin invocar fastembed. 4. MISS: ente_cas::store(&buf) (write-through, no-fatal si falla) -> backend.embed_one(text) -> cache.put(...). Backend de cache: sled local en $XDG_CACHE_HOME/brahman/nouser-nous-real-embed-cache.sled. Tree versionado embed_cache_v1; el MODEL_ID viaja en la key, asi que cambiar de modelo invalida el cache implicitamente. Override por env NOUSER_NOUS_REAL_CACHE. Encoding compacto: cada Vec<f32> se serializa como bytes little-endian (4B por f32, sin overhead). Para 384-d son 1.5 KiB por entry. Decode tolera bytes corruptos (longitud no-multiplo de 4 -> None, no panic). Por que sled y no ente-cas directo: el CAS de arje es flat sha256-keyed; la cache necesita un mapeo (file_sha, model_id) -> embedding, no expresable como entry CAS. El write-through a CAS queda como registro consultable + futura GC. Mock NO se modifica — su embedding pseudo-32d es metadata-hashing puro, sin costo. Cachearlo seria overhead. Tests: 5 unitarios verdes (roundtrip, miss, model collision, content collision, corrupted value). Stub mode (sin feature) sigue compilando sin tocar cache. |
||
|
|
79d42aba28 |
chore(nakui): alinear nakui-core con [workspace.package] y deps compartidas
Cleanup de drift de convenciones: nakui-core era el unico crate del
monorepo que manteia version, edition y thiserror hardcoded, mientras
el resto heredaba del workspace y usaba thiserror v2. Eso significaba
que un bump global de version o edition se olvidaba sistematicamente
de nakui.
Cambios:
- [package]: version, edition, rust-version, license, authors, publish
-> todos *.workspace = true. Agregado description (convencion).
- Deps compartidas migradas a { workspace = true }: serde, serde_json,
thiserror (v1->v2), tokio, ulid, sha2.
- uuid migrado a { workspace = true, features = ["serde"] } — la feature
serde no esta en el workspace dep porque nakui es el unico user;
queda local opt-in en lugar de inflar el dep comun.
- Deps especificas de nakui (sin comparticion posible): rhai, petgraph,
surrealdb permanecen inline con version local.
Verificacion: cargo build -p nakui-core verde tras el bump thiserror
v1->v2 — los 14+ enums de error de nakui no requirieron ajustes
(derive backwards-compat para patrones simples). cargo test -p
nakui-core --lib: 27/27 verdes.
Bonus en este commit: discovery.rs movio el import Ulid a #[cfg(test)]
porque el refactor a Card::new lo dejo unused en module-scope.
|
||
|
|
4c9e4c3962 |
feat(card): Card::new(label) — alternativa segura a Default::default()
Cierra la trap documentada de Card::default() que devuelve id =
Ulid::nil(). Usar Card::default() viva colisionaba con cualquier otra
Card default-construida bajo el mismo id 00000...
La fix no es romper Default (sigue siendo determinista, requerido por
callers que lo usan como template para deserializacion y patterns de
busqueda) sino agregar un constructor explicito Card::new(label) que
asigna id = Ulid::new() + label provisto, manteniendo defaults seguros
en todo lo demas.
Pensado para struct-literals con override parcial:
let card = Card {
kind: CardKind::Data,
payload: Payload::Embedded(json),
..Card::new("mi-modulo.algo")
};
Refactor de call sites en codigo de produccion:
- brahman_sidecar::discovery::build_consumer_card
- nouser daemon::build_engine_card
Default queda con docstring expandida que apunta a Card::new para uso
"vivo". to_brahman_card en nouser-card NO se modifica porque asigna
el id estable de la Monada, no uno fresco.
Tests: 3 unitarios nuevos en brahman-card. 15 tests verdes (era 12).
|
||
|
|
006640057a |
feat(sidecar): API reusable de discovery via broker
Promueve el patron ad-hoc discover_producer_socket que vivia inline en
'nouser attract --remote' a un modulo publico brahman_sidecar::discovery.
Cualquier consumer ahora puede preguntar al broker "quien provee este
TypeRef?" sin reimplementar el patron a mano.
API:
- build_consumer_card(label, flow_name, type_name) construye una Card
minima (Ente, Oneshot, Virtual) con un input flow. Asigna Ulid::new()
real (no nil), evitando colisiones en el broker.
- await_provider(card, timeout) async: conecta al init, espera
MatchEvent::Available, devuelve producer_service_socket, manda
Farewell. Ignora eventos Lost durante el await.
- await_provider_blocking(card, timeout) wrapper para mundos no-async
(CLIs, std-thread loops). Crea su propio runtime current_thread.
- ConsumerError tipado: Connect{socket,source}, NoProvider{flow,type_ref,
timeout}, Client(ClientError), Runtime(String). Adios al Box<dyn Error>.
Refactor en nouser daemon: discover_producer_socket inline (60 LOC) ->
5 LOC delegando en el helper. remote_embed ya no construye su propio
runtime.
Tests: 4 unitarios (id no-nil, id unico por llamada, formateo de Wit
TypeRef, fallback sin input). Build verde para sidecar y nouser-core.
|
||
|
|
2725d6a297 |
feat(nouser+sidecar): watcher con debounce 150ms + re-publish al broker
Cierra los dos pendientes documentados en
|
||
|
|
487c457e5b |
feat(nouser): notify watcher — el sistema reacciona en tiempo real
El daemon monta notify::recommended_watcher recursivo sobre el dir escaneado. Cada Create/Modify de archivo regular dispara: embedding → filtro por centroid_model → ranking contra centroides → log con 🧲 / · según supere DEFAULT_ATTRACTION_THRESHOLD. $ nouser daemon /tmp/x & $ vim /tmp/x/src/nuevo.rs [watcher] 🧲 /tmp/x/src/nuevo.rs → x/src (0.7470) $ echo edit >> /tmp/x/docs/n1.md [watcher] 🧲 /tmp/x/docs/n1.md → x/docs (0.8169) Mecánica: - DB pasa a Arc<Mutex<MonadDb>> para sharing con thread watcher. - Watcher en thread dedicado nouser-watcher; reacciona sólo a Create/Modify, ignora Access/Metadata-only. - react_to_change(path, metadata, db) computa embedding, filtra por centroid_model, busca best attraction. - No re-publica al broker ni muta DB — sólo observa y narra. La invalidación selectiva (re-cluster + replace + diff publish) queda para futuro. Limitación conocida: notify emite múltiples eventos por edición (Create + Modify, etc.). Sin debounce el watcher reporta varias veces. Aceptable para demo; producción conviene debounce ~100ms por path. Esto cierra la Fase C del plan post-reporte: el sistema "se siente" vivo. Tocar un archivo en vim y ver inmediatamente la atracción calculada cumple el meta-mensaje "Mónada Viva". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
65af98da13 |
feat(nouser): hidratación del daemon vía sled + path_hint
El daemon ya no recomputa ciegamente al arrancar. Si la DB tiene Mónadas previas con centroid_model válido, las publica instantáneo y el re-scan reusa sus IDs vía path_hint. Schema: - MonadManifest.path_hint: Option<String> — identidad estable derivada del origen (para by_directory, el parent dir canónico). Permite reusar ULID across re-scans. Cluster: - Nueva fn cluster::by_directory_hydrated(files, min_files, prior). Con prior, busca Mónada con mismo path_hint Y mismo centroid_model; si la encuentra, reusa id, lineage y created_at_ms. - by_directory queda como wrapper sin hidratación (back-compat). Daemon (cmd_daemon): 1. Open sled si NOUSER_DB_PATH existe. 2. Publica Mónadas previas con centroid_model válido (las inválidas se descartan con log explícito). 3. Re-scan + by_directory_hydrated(prior=&db). 4. Sólo spawnea sidecars para Mónadas con id NUEVO. Los path_hints existentes preservan identidad, evitando duplicados en el broker. 5. Persiste el set actualizado. Validación: $ NOUSER_DB_PATH=/tmp/h.sled nouser daemon crates/core # arranque 1: re-scan 102 archivos → 5 mónadas (5 nuevas) $ NOUSER_DB_PATH=/tmp/h.sled nouser daemon crates/core # arranque 2: hidratadas 5 mónadas en O(1) # re-scan → 5 mónadas (0 nuevas vs hidratación) Costo del arranque 2: ~0.06s user CPU. Tests: 7 (card) + 24 (core) verdes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
820a1a33bf |
feat(nouser): centroid_model — versionado de embeddings
Protege contra el bug silencioso de mezclar centroides de modelos
distintos (mock 32-d vs real 384-d), que daría scores sin sentido.
- MonadManifest.centroid_model: Option<String>. None = legacy.
- nouser_core::embed::MODEL_ID = "nouser-pseudo-32d". Cluster lo
setea en cada Mónada que genera.
- nouser-nous-mock reusa la misma constante (use
nouser_core::embed::MODEL_ID): produce vectores idénticos al
cluster local, reportar el mismo ID es honesto.
- nouser-nous-real ya reportaba "real-fastembed-allMiniLML6V2-384d";
el filter ahora lo descarta automáticamente cuando los centroides
cacheados son del mock.
- cmd_attract:
- Captura el model_id del embedding del target.
- Filtra Mónadas cuyo centroid_model no matchee.
- Reporta "embed: <source> (<model>)" y "skipped: N" cuando
descarta.
Resultado: cambiar de mock a real vía BRAHMAN_BROKER_CONTEXT=prod
hace que attract filtre las Mónadas viejas con cero score en lugar
de fingir que las puede comparar.
Tests: 7 (card) + 24 (core) verdes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
9c371ee43e |
feat: profile.dev slim + dynamic binding del consumer Nous
Dos piezas del plan post-reporte, en un commit por estar acopladas
(ambas tocan cómo se construye y conecta el sistema):
profile.dev slim:
- debug = "line-tables-only" + split-debuginfo unpacked +
codegen-units 256 en [profile.dev].
- Override [profile.dev.package.{gpui,ort,fastembed,tokenizers,image}]
con opt-level=1, debug=false para los pesados que no debuggeamos.
- Resultado: binarios ~3× más livianos. ente-zero 125→47 MB;
mock-nous ~50→22 MB. target/ futuro mucho más manejable.
dynamic binding (cierra priority_contexts):
- nouser-core Cargo.toml: deps directas brahman-handshake + tokio.
- cmd_attract refactor:
- Si NOUSER_NOUS_SOCKET está set, atajo explícito (compat).
- Si no, abre Client al brahman-init, anuncia consumer Card con
flow.input = embed-result:json, espera 3s por MatchEvent::Available,
usa producer_service_socket del evento.
- discover_producer_socket() es async; cmd_attract usa runtime tokio
current_thread inline (block_on).
- embed_via(path, file) se separa como helper sync para la RPC.
Validación end-to-end:
$ ente-zero & nouser-nous-mock &
$ nouser attract --remote crates/core archivo.rs
🧲 0.9058 ente-brain/src ...
(mock log: "embed_file path=archivo.rs" — discovery activo)
Con esto BRAHMAN_BROKER_CONTEXT=test/prod swappea el provider sin que
el consumer toque nada — la promesa de priority_contexts es real.
Bug colateral resuelto: la "flakiness" del cargo test --workspace era
disco lleno (24 GB en target/), no condición de carrera. Con
cargo clean + profile slim, tests deterministas.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
5c41ef920d |
feat(nouser): yahweh widget — nouser-explorer panel GPUI
Bin GPUI standalone que consulta brahman-admin cada 2s y renderea
todas las sesiones del Init como cards. Cierra el círculo visual
del ecosistema brahman.
- Crate nuevo crates/apps/nouser-explorer (deps: brahman-admin,
brahman-card, gpui).
- Ventana 900x640 con header del estado del Init, banner de error
cuando no conecta, y lista de cards (una por sesión).
- Cada card muestra: kind + label + lifecycle, ULID corto, summary
(si data), keywords, lens hint, service_socket si está, y refs
(RelationshipKind → target_label). El borde izquierdo coloreado
diferencia ente (azul) de data (lavanda).
- cx.spawn(async move |this, cx| { ... }) corre el loop de refresh
en el GPUI executor; query_blocking porque GPUI no tiene runtime
tokio.
- Helper nuevo en brahman-admin: client::query_blocking(path) —
versión sync de query(), para callers con su propio executor.
Uso:
$ ente-zero & nouser daemon crates/core &
$ cargo run -p nouser-explorer
# ventana 900x640, ~6 cards en vivo, refrescando cada 2s.
cargo check --workspace: 0 errores, 0 warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
7831c0c827 |
feat(nouser): persistencia sled write-through del MonadDb
MonadDb ahora soporta backend dual: - MonadDb::new() → memoria pura (default, back-compat). - MonadDb::open(path) → sled-backed con cache en memoria. Carga contenido existente al abrir; cada insert_* hace write-through (cache + sled). Diseño: - 2 trees sled: files y monads. - Wire format: serde_json (ergonomía + inspectability con sled-cli; los manifests son chicos, JSON gana sobre postcard aquí). - Reads SIEMPRE desde la cache — sled se consulta sólo al abrir. - replace_monads() purga el tree de sled antes de escribir. Bin nouser: nueva env var NOUSER_DB_PATH. Si está set, persiste; si no, in-memory: $ NOUSER_DB_PATH=/tmp/monads.sled nouser scan crates/core scan: 102 archivos, 5 mónadas $ ls /tmp/monads.sled blobs conf Tests nuevos en db.rs: - persistence_roundtrip — escribe, cierra, reabre, datos están. - replace_monads_purges_persistent_tree — replace limpia tree. 24 tests en nouser-core (era 22, +2). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d7b4886164 |
feat(sidecar): Phase B-3 — SidecarPool consolida sidecars en un runtime
Antes: cada spawn(card) creaba un thread + tokio runtime propio.
Para módulos con muchas sesiones (nouser daemon con 50+ Mónadas)
eso es 50 threads + 50 runtimes. Ahora: un thread + un runtime
tokio current_thread que hostea N tasks de sidecar.
API nueva (aditiva, no rompe spawn/spawn_with_handle):
let pool = SidecarPool::new()?;
pool.spawn(card1);
pool.spawn(card2);
pool.spawn_conscious(card_with_wit, wit);
pool.spawn_with_config(custom_config);
// pool drop = todas las sesiones cierran.
run_client se hace pública para que el pool pueda enqueuar tasks
externos al runtime con handle.spawn(run_client(config)).
nouser daemon migrado al pool. Verificación con ps -L:
$ ps -L -p $(pidof nouser)
LWP CMD
28817 nouser # main thread
28819 brahman-sidecar # pool thread (todas las sesiones)
Antes serían 6+ LWP (1 main + N sesiones). Ahora 2 fijos sin
importar cuántas Mónadas se publiquen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
b3feaf667c |
feat: Crossreferencia — Card.references como grafo del fractal
Las Cards ahora declaran sus relaciones con otras Cards. El Engine
posee Mónadas; las Mónadas declaran que son poseídas por el Engine.
- brahman-card:
- RelationshipKind { Owns, OwnedBy, Processes, ProcessedBy, Sibling }
- CardReference { kind, target_id: Ulid, target_label: String }.
target_label es cache para que la UI renderee sin resolver.
- Card.references: Vec<CardReference> + espejo en WireCard.
Conversiones From propagan.
- brahman-broker::BrokeredCard propaga references.
- brahman-status imprime "ref OwnedBy → label (id)" por sesión.
- nouser daemon: cada Mónada publicada añade OwnedBy apuntando al
engine. Declaración unilateral — el engine no necesita conocer
Mónada IDs de antemano.
Validación end-to-end:
$ ente-zero & nouser daemon crates/core
$ brahman-status
Sessions (6):
[ente] brahman.nouser_engine
[data] brahman-handshake/src
ref OwnedBy → brahman.nouser_engine (01K...)
[data] ente-brain/src
ref OwnedBy → brahman.nouser_engine (01K...)
...
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
5edc912ed8 |
feat: Phase D-3 + D-4 — service_socket en Card, providers coexisten
Cierra el ciclo del swap automático Nous mock↔real:
- brahman-card: Card.service_socket: Option<PathBuf> y espejo en
WireCard. Path del data plane (distinto al Init). Cualquier
consumer que matchee con esta Card conecta directo, sin discovery
extra.
- brahman-broker: BrokeredCard propaga service_socket. Sin
participación en matching — sólo metadata.
- brahman-handshake::MatchEvent: nuevo campo
producer_service_socket. Server lo busca en BrokeredCard al emitir
Available.
- nouser-nous::transport: provider_socket_path(provider: &str)
devuelve nouser-nous-{provider}.sock por default. Mock y real
coexisten en sockets distintos (Phase D-4). default_socket_path()
conserva el comportamiento single-provider.
- Mock declara nouser-nous-mock.sock; real declara
nouser-nous-real.sock. La Card se construye DESPUÉS del bind.
- brahman-status imprime "socket:" por sesión cuando está presente.
Validación end-to-end:
$ ente-zero & nouser-nous-mock & nouser-nous-real &
$ ls /run/user/1001/nouser-nous-*.sock
nouser-nous-mock.sock
nouser-nous-real.sock
$ brahman-status
Sessions (2):
[ente] nouser.nous_real
socket: /run/user/1001/nouser-nous-real.sock
[ente] nouser.nous_mock
socket: /run/user/1001/nouser-nous-mock.sock
Pendiente (no crítico): nouser-core attract --remote usa todavía
NOUSER_NOUS_SOCKET hardcoded. Siguiente paso: subscribirse al
MatchEvent del broker y usar producer_service_socket directo, así
BRAHMAN_BROKER_CONTEXT=test/prod swapea provider sin tocar al
consumer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
794884a90f |
refactor(nouser): labels de Mónada con 2 componentes del path
Resuelve la fricción visual de monorepos donde múltiples Mónadas
quedaban con label "src" (ambiguo). Nueva función label_from_path
toma los últimos hasta 2 componentes normales del path:
$ nouser scan crates/core
[01K..] brahman-admin/src card=5
[01K..] brahman-handshake/src card=6
[01K..] ente-brain/src card=11
[01K..] ente-kernel/src card=4
Tests añadidos: label_from_root_only_one_component,
label_from_deep_path_takes_last_two. Tests existentes actualizados
con los nuevos labels (proj/src en lugar de src).
22 tests en nouser-core (era 20, +2).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
11fc95629c |
feat(nouser): Phase D-2 — proveedor Nous real (LLM) detrás de feature
Cierra el ciclo del módulo Nous: existe un proveedor que produce
embeddings reales con un modelo LLM, mientras que `cargo build` sin
features sigue siendo liviano (no descarga ni compila ML deps).
Crate nuevo crates/modules/nouser/nous-real con dos modos según feature:
- Sin feature (default): stub.
cargo build -p nouser-nous-real (~10s, sin ML deps).
Bin arranca, sidecarea a brahman-init declarando la Card,
escucha en el socket Nous, rechaza requests con un ErrorResponse
explicativo: "compilado sin la feature embeddings, rebuild con
cargo build -p nouser-nous-real --features embeddings".
cargo build --workspace SIGUE siendo limpio.
- Con --features embeddings: real.
Pulls fastembed = "4" → ort 2.0.0-rc.9 (ONNX Runtime con binarios
descargados por Cargo) + tokenizers 0.21 + ~30 transitive deps.
Compila en ~50s.
Modelo default: all-MiniLM-L6-v2 (384-d, descargado a
~/.cache/fastembed la primera vez).
EmbedText: pasa el texto al modelo → vector 384-d.
EmbedFile: lee primeros 8KiB UTF-8 lossy, embed como texto.
Ping: devuelve model_id + embed_dim reales.
Card declara label "nouser.nous_real" + priority_contexts.prod = +1.
En contexto prod gana sobre el mock; en test el mock gana por su +1
en test. Sin contexto, empate alfabético.
Validación end-to-end con modelo real:
$ ente-zero & nouser-nous-real &
$ python3 socket-probe '{"kind":"embed_text","payload":{"text":"..."}}'
model: real-fastembed-allMiniLML6V2-384d
elapsed_ms: 8
embed_dim: 384
Tradeoff: dim mock (32) vs real (384) son incompatibles. Cambiar
proveedor invalida centroides cacheados — documentar "limpiar DB al
swap".
Workspace state:
- cargo build --workspace limpio sin features (no ML deps pulled).
- cargo build -p nouser-nous-real --features embeddings funciona.
- 0 errores, 0 warnings en ambos modos.
Pendientes para D-3 / futuro:
- Discovery de socket: el consumer hoy usa NOUSER_NOUS_SOCKET hardcoded.
Para que el broker elija real vs mock per-contexto, falta o un campo
socket en el MatchEvent o un broker query "dame socket de session X".
- Coexistencia: ambos providers compiten por el mismo socket path por
default. Parametrizarlos cuando se quiera correrlos juntos.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
b3c3c00cf2 |
feat(nouser): Phase D — proveedor Nous mock + cliente remoto
Cierra el patrón "Nous como módulo aparte intercambiable": el contrato
del proveedor de embeddings vive en su crate, el mock determinístico
implementa ese contrato sirviéndolo por Unix socket, y nouser-core
sabe consumirlo remotamente. El switch mock↔real (futuro) será vía
priority_contexts en el broker.
Crates nuevos:
- crates/modules/nouser/nous: contrato compartido.
- EmbedRequest { kind: { EmbedFile | EmbedText | Ping }, payload }.
- EmbedFilePayload (path, ext, size, mtime), EmbedTextPayload.
- EmbedResponse (embedding, model, elapsed_ms), PingResponse,
ErrorResponse.
- Wire: line-delimited JSON sobre Unix socket, single-shot.
- Constants FLOW_EMBED_REQUEST, FLOW_EMBED_RESULT, FLOW_TYPE_NAME.
- transport::default_socket_path con env NOUSER_NOUS_SOCKET.
- crates/modules/nouser/nous-mock: bin nouser-nous-mock.
- Sidecarea a brahman-init con Card kind=Ente declarando los flows
embed-request/embed-result + priority_contexts.test = +1.
- Bind del socket Nous + accept loop tokio.
- EmbedFile delega a nouser_core::embed::embed (Phase C).
- Modelo: "mock-pseudo-32d".
Cambios:
- nouser-core: dep nueva nouser-nous. Subcomando attract --remote
abre un UnixStream blocking, envía EmbedRequest, lee response.
Imprime "embed: local|remote" para ver cuál ruta corrió.
Bug encontrado y corregido:
- ContextBias tenía #[serde(skip_serializing_if = ...)] en sus campos.
Postcard NO soporta skip-condicional en formatos no self-describing:
el serializer omitía bytes que el deserializer esperaba, rompiendo
la wire de cualquier Card con priority_contexts poblada.
Síntoma: "postcard decode: Hit the end of buffer" en el server,
"early eof" en el cliente.
- Fix: removidos los skip_serializing_if de ContextBias. JSON pretty
ahora emite {"pin_to": null, "priority_offset": 0} pero el wire
funciona. Trade-off aceptado.
- Test wirecard_postcard_with_priority_contexts en brahman-card que
ejercita el roundtrip postcard con biases poblados.
Validación end-to-end:
$ ente-zero & nouser-nous-mock & nouser daemon crates/core
$ brahman-status
Sessions (7):
[ente] nouser.nous_mock flows: embed-request, embed-result
[ente] brahman.nouser_engine
[data] src summary: 6 archivos en crates/core/brahman-handshake/src
[data] graph summary: 7 archivos en crates/core/ente-zero/src/graph
...
$ nouser attract --remote crates/core <archivo>.rs
embed: remote
🧲 0.9058 src ...
(mock log: embed_file path=...)
Tests: 75. cargo check --workspace: 0 errores, 0 warnings.
Próximo natural: Phase D-2 — real-nous con ONNX/Llama text-embedding.
Declara la misma Card con priority_contexts.prod = +1 y el swap es
transparente para el consumer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
77faf12e82 |
feat(nouser): Phase C — pseudo-embeddings + atracción por centroide
El "imán semántico" matemático del diseño Kairos, sin LLM. Cada
archivo se proyecta a un vector 32-d determinista derivado de sus
metadatos; cada Mónada calcula su centroide; archivos nuevos se
asignan por cosine similarity contra los centroides existentes.
Cambios:
- nouser-core dep nueva: blake3 (hash determinista de strings).
- crates/modules/nouser/core/src/embed.rs nuevo:
- EMBED_DIM = 32. Vector:
* dims 0..8: blake3(extension)
* dims 8..16: blake3(parent_dir)
* dims 16..24: blake3(file_stem)
* dims 24..28: tamaño (log + flags)
* dims 28..32: mtime (escala día + features cíclicas)
- Tip clave: hash bytes se centran a [-1, 1] (no [0, 1]). Sin
centrar, dos hashes random tendrían cosine ~0.75 espurio.
Centrados, expectativa ≈ 0 entre no-relacionados.
- APIs: embed, cosine_similarity, centroid, cohesion,
attraction_score, best_attraction. DEFAULT_ATTRACTION_THRESHOLD = 0.7.
- cluster::by_directory ahora computa el centroide de cada Mónada
y lo guarda en MonadManifest.centroid. El centroide viaja al
brahman-status vía DataFacet.centroid.
- bin nouser nuevo subcomando: attract <dir> <file>.
- Scan del dir, embedding del archivo objetivo, ranking de afinidad
contra Mónadas con centroide.
- 🧲 si la mejor supera umbral, · si es mejor pero debajo.
Validación end-to-end:
$ nouser attract crates/core crates/modules/nouser/core/src/embed.rs
🧲 0.9058 [01K..] src (ente-brain/src)
0.8984 [01K..] src (brahman-handshake/src)
...
$ nouser attract crates/core crates/modules/nouser/core/Cargo.toml
0.3427 [01K..] graph (ente-zero/src/graph)
(mejor score 0.3427 < umbral 0.7000 — no se 'pega')
7 tests nuevos en embed (determinismo, normalización, similitud
mismo-dir/mismo-ext, baja entre no-relacionados, centroide
unidad+coherente, attraction picks correctly, vacío skipeado).
Tests acumulados: 73. cargo check --workspace: 0 errores, 0 warnings.
Próximo: Phase D — nouser-nous, módulo aparte para LLM real.
Mock-nous determinista (basado en estos pseudo-embeddings) en
BRAHMAN_BROKER_CONTEXT=test; real-nous en prod. El switch lo hace
el broker via priority_contexts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
85886b7a3c |
feat(nouser): Phase B-2 — daemon que publica Mónadas al Init brahman
Cierra la unificación ontológica de B-1: el nouser daemon se
sidecarea como Ente y publica cada Mónada como su propia sesión Data.
Un solo brahman-status muestra procesos y datos en la misma lista.
Cambios:
- nouser-core gana deps brahman-card + brahman-sidecar.
- bin nouser nuevo subcomando: daemon <dir>.
1. Spawna sidecar para el engine (brahman.nouser_engine, kind=Ente):
el "ser" que produce y administra Mónadas.
2. Scan + cluster del directorio.
3. Para cada Mónada, monad.to_brahman_card() + sidecar (kind=Data).
Cada Mónada es una sesión brahman propia, con su ULID estable.
4. Park del main thread; los sidecars siguen pingueando.
Validación end-to-end:
$ ente-zero &
$ NOUSER_MIN_FILES=5 nouser daemon crates/core &
$ brahman-status
Sessions (6):
[ente] ... brahman.nouser_engine lifecycle=Daemon
[data] ... src summary: 5 archivos en crates/core/brahman-admin/src
members: 5 (dispersion=0.00) lens hint: code
[data] ... src summary: 11 archivos en crates/core/ente-brain/src
[data] ... graph summary: 7 archivos en crates/core/ente-zero/src/graph
...
La función de presentarse es la misma para procesos y datos. UI ve
una lista uniforme y discrimina por `kind` cuando le importa.
Costo conocido: cada Mónada consume thread + tokio runtime
current_thread (legacy del sidecar API). Para escalar a >50 Mónadas
conviene consolidar en un único runtime con N tasks. Defer a B-3.
Pendientes propuestos (en CHANGELOG):
- B-3: consolidar sidecars en un solo runtime.
- C: pseudo-embeddings + atracción por centroide.
- D: módulo nouser-nous para LLM, swappable por priority_contexts.
- Polish: labels con 2-3 componentes de path.
- Crossreferencia: Ente anuncia "procesando Mónada X", Mónada anuncia
"siendo procesada por Ente Y".
cargo check --workspace: 0 errores, 0 warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
b85700c538 |
feat: Phase B-1 — unificación ontológica de Cards (Ente ↔ Data)
La Card pasa a ser EL protocolo de presentación del ecosistema. Una
Mónada Nouser y un Ente Brahman son ambos "entidades que se presentan";
el consumidor (UI, broker, admin) discrimina por `kind` cuando importa,
pero todos hablan el mismo idioma.
brahman-card:
- CardKind { Ente (default), Data }. Backward-compat: Cards existentes
quedan Ente.
- DataFacet { summary, keywords, centroid, member_count, dispersion,
presentation_hint } — vista liviana para el wire. Listas grandes
(members reales, embeddings completos) se consultan al daemon dueño
bajo demanda.
- Card.kind y Card.data agregados. WireCard espeja, conversiones From
propagan ambos campos.
- Default impl actualizado.
brahman-broker:
- BrokeredCard propaga kind y data desde la Card registrada. No afecta
el matching (sigue por TypeRef + priority + pin_to); permite a
observadores discriminar sin re-query.
nouser-card:
- Depende ahora de brahman-card.
- MonadManifest::to_brahman_card() proyecta una Mónada a Card brahman:
- id, label, lineage directos.
- payload Virtual, supervision Delegate, lifecycle Daemon
(placeholder — la Mónada no se ejecuta).
- kind = Data.
- data = Some(DataFacet { summary, keywords, centroide,
member_count, entropy → dispersion, presentation_hint del Lens }).
- Test nuevo projects_to_brahman_card.
brahman-status:
- Prefijo [ente] o [data] por sesión.
- Sesiones data renderean también summary, members + dispersion,
keywords y lens hint.
Resultado: la UI ve una sola lista uniforme — no necesita saber si
mira procesos o cúmulos de datos, sólo lee el Card y se adapta por
kind. La función de presentarse es la misma para todos.
Tests: 59 (card 11, broker 15, handshake codec+tr 2 + integ 7,
card-wit 4, admin 0, nouser-card 7 +1, nouser-core 13).
cargo check --workspace: 0 errores, 0 warnings.
Próximo: Phase B-2 — bin nouser daemon que sidecarea cada Mónada como
sesión brahman, mezclándolas con los entes en brahman-status.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
7bdc26e61a |
feat(nouser): Phase A — mecanismo determinista de Mónadas
Primer trozo de Nouser/Kairos: explorador de Mónadas como agrupaciones
semánticas sobre el filesystem, sin tocar IA todavía. Cubre el 90% de
los casos con heurísticas puras.
Crates nuevos:
crates/modules/nouser/card:
- MonadManifest: la Tarjeta de Presentación de una Mónada. Espejo
conceptual de brahman::Card pero para datos: id (Ulid), label,
summary, centroid (vacío en Phase A), keywords, cardinality, entropy
[0,1], dominant_lens (Grid|Code|Gallery|Database|Markdown|Tree),
pins, members, timestamps, extensions (forward-compat).
- Diferencia explícita en docs: brahman::Card describe entidades
runtime con payload/soma/supervision; MonadManifest describe una
agrupación de datos sin proceso atrás.
- Validación: schema_version, label no vacío, entropy en rango,
cardinality consistente con members.len().
- 6 tests (validación + JSON roundtrip).
crates/modules/nouser/core:
- scanner::scan_directory: walkdir → Vec<FileEntry> con metadatos.
Skipea hidden por default; configurable max_depth y follow_links.
- cluster::by_directory: agrupa archivos por parent dir, mínimo 3
para promover a Mónada (configurable). Computa keywords (top-N
extensiones por freq + alfabético), elige Lens dominante por
extensión más frecuente, entropía de Shannon normalizada.
- db::MonadDb: store en memoria con índices BTreeMap.
resolve_members filtra IDs huérfanos.
- bin nouser con subcomandos scan, show, json. Env var
NOUSER_MIN_FILES para el threshold.
- 13 tests (4 scanner + 6 cluster + 3 db).
Demo end-to-end:
$ nouser scan crates
scan: 255 archivos en crates, 19 mónadas (min_files=3)
[01KR4C13] src card=12 ent=0.00 lens=Code keywords: rs
[01KR4C13] tests card=14 ent=0.00 lens=Code keywords: rs
[01KR4C13] fixtures card=5 ent=0.00 lens=Grid keywords: rhai
Pendientes (anotados en CHANGELOG, no urgentes):
- Phase B: bin nouser daemon que sidecarea a brahman-init.
- Phase C: pseudo-embeddings de metadatos + atracción por centroide.
- Phase D: módulo nouser-nous para el LLM real, swappable por
priority_contexts (mock-nous en test, real-nous en prod).
- Polish: labels con 2-3 componentes del path.
cargo check --workspace: 0 errores, 0 warnings.
Tests acumulados: 58.
CHANGELOG.md actualizado.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
bbb9a9d2f5 |
feat(broker): priority contexts — biases per-contexto operativo
Cierra el último pendiente de feature: el broker ahora puede operar
bajo un contexto (test/prod/foreground/secure/etc) que activa biases
declarados en las Cards.
Schema (brahman-card):
- ContextBias { pin_to: Option<String>, priority_offset: i8 }.
- Card.priority_contexts: BTreeMap<String, ContextBias>, también en
WireCard. Las conversiones From propagan el campo.
Comportamiento (brahman-broker):
- BrokerConfig.current_context: Option<String>. Cuando es Some(ctx) y
una Card tiene priority_contexts.get(ctx), el bias aplica:
- Consumer-side: bias.pin_to sobreescribe Flow.pin_to estático.
- Producer-side: bias.priority_offset se suma a la priority base
(clamp en [Low=0, Critical=3]).
- BrokeredCard propaga priority_contexts. find_producer_for usa
effective_priority y context_bias en lugar de comparar Priority
directo.
Observabilidad:
- AdminConfig.current_context + StatusSnapshot.current_context.
- brahman-status imprime "Context: <nombre>" si está activo.
Wiring:
- ente-zero lee BRAHMAN_BROKER_CONTEXT del entorno y la propaga al
broker y al admin. Sin var, biases inactivos (back-compat total).
Tests nuevos (brahman-broker, +4):
- context_priority_offset_lifts_producer_above_alphabetic_winner:
sin contexto a-prod gana por alfabético; con context "test" b-prod
gana por offset +1.
- context_pin_to_overrides_static_pin: static pin "real-dht", test
override "mock-dht" → mock gana en context "test".
- unknown_context_no_op: biases declarados para "test" no aplican
cuando broker está en "prod".
- priority_offset_clamps_to_critical: offset enorme se clampa a 3.
Validación end-to-end manual:
$ BRAHMAN_BROKER_CONTEXT=test ente-zero &
$ brahman-status
Init: server=0.1.0 protocol=0.1.0 attached=true
Context: test
Tests acumulados: 39 (card 11, broker 15, handshake codec+transport 2 +
integ 7, card-wit 4, admin 0). cargo check --workspace: 0 errores, 0
warnings.
Con esto cierran TODOS los pendientes técnicos abiertos. El único
"pendiente" que queda es el caso real para extender (priority
contexts per-deployment, scheduling biases dinámicos, etc.).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
f19ca723b6 |
feat(card): WireCard + extensions — forward-compat sin romper postcard
Restaura el campo extensions de Card que había caído al adoptar postcard (serde_json::Value usa secuencias/maps de longitud dinámica). La solución es separar dos formas: - Card (la rica): para JSON/TOML. Tiene extensions: BTreeMap<String, serde_json::Value> con #[serde(flatten, skip_serializing_if = is_empty)]. Los campos desconocidos del archivo sobreviven el roundtrip. - WireCard (la slim): para postcard. Mismo schema sin extensions y con genesis: Vec<WireCard> recursivo. Postcard-friendly por construcción. Conversiones From<Card> for WireCard (descarta extensions) y From<WireCard> for Card (extensiones quedan vacías post-wire). El contrato es explícito: extensions son anotaciones locales que sobreviven file I/O pero NO cruzan al Init. brahman-handshake::Hello.card cambia de Card a WireCard. Client hace card.into() al enviar; Server hace hello.card.into() para volver a Card antes de validar/registrar. Tests: - 3 nuevos en brahman-card: extensions_preserved_in_json_roundtrip, wire_card_roundtrip_strips_extensions, wire_card_postcard_friendly (verifica que postcard::to_allocvec(&wire) NO falla — caso que rompía con Card.extensions populadas). - 1 ajuste en handshake/tests/handshake.rs (struct-literal de Hello ahora con card: sample_card(...).into()). - brahman-card: postcard como dev-dep. Tests acumulados: 35 (card 11, broker 11, handshake codec+transport 2 + integ 7, card-wit 4, admin 0). 0 errores, 0 warnings (vienen del commit anterior |
||
|
|
9420eae0b6 | probando | ||
|
|
354f992c63 |
feat(sidecar): WIT al sidecar — módulos conscientes vivos
Cierra el ciclo brahman-card-wit ↔ runtime: un módulo que tenga su .wit lo parsea, lo manda en Hello, y aparece como "consciente" en el broker y en brahman-status. Cambios coordinados (un solo commit por la cadena de tipos): - brahman-card::WitInterface deriva Serialize/Deserialize/Eq. - brahman-handshake::Hello lleva wit: Option<WitInterface> (#[serde(default)] para tolerar Hellos antiguos en formato JSON aunque postcard exige presencia explícita). - Server's register_session enruta a ResolvedCard::from_conscious cuando viene wit; from_agnostic cuando no. - Client::connect queda como wrapper de connect_with(path, card, wit: Option<WitInterface>) — backward-compatible. - Broker::register acepta Option<WitInterface> como tercer arg; BrokeredCard guarda el wit. 25 sitios de tests actualizados con `, None` (vía perl). - brahman-sidecar::SidecarConfig.wit + helpers SidecarConfig::with_wit y spawn_conscious(card, wit). Log attached reporta conscious=true|false. - brahman-status pretty-print con 🧠 + sección wit (package/world + imports + exports) por sesión consciente. - Example nuevo presence-conscious: parsea protocol.wit y se presenta consciente. Validación end-to-end manual: $ ente-zero & $ presence-conscious demo.conscious shared_wit/protocol.wit & $ brahman-status Sessions (1): 01K... demo.conscious 🧠 lifecycle=Daemon wit: brahman:protocol@0.1.0 / module imports: types, handshake, lifecycle exports: run Tests: 32/32 (broker 11 + card 8 + handshake codec+transport 2 + integ 7 + admin 0 + card-wit 4). Workspace: 0 errores. CHANGELOG.md actualizado. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
f4dc019004 |
feat(core): brahman-card-wit — extractor opcional de contratos WIT
Crate nuevo crates/core/brahman-card-wit que parsea texto WIT con
wit-parser y devuelve un Vec<WitInterface> (de brahman-card),
listo para acoplarse a una ResolvedCard::from_conscious(card, wit).
Ámbito intencional: sólo parsing texto, no toca wasm-tools ni
wit-component. Es opt-in: brahman-card no depende de éste.
API pública:
- parse_wit(source: &str) -> Result<Vec<WitInterface>, WitError>
- parse_wit_file(path: impl AsRef<Path>) -> Result<...>
Cada WitInterface incluye: package, world, imports, exports.
Las interfaces importadas/exportadas (no sólo funciones) se
resuelven por nombre via resolve.interfaces[id].name; las
funciones inline aparecen como WorldKey::Name directo.
Example CLI: brahman-wit-info <ruta.wit> imprime los worlds.
$ brahman-wit-info shared_wit/protocol.wit
2 world(s):
package: brahman:protocol@0.1.0
world: module
imports: types, handshake, lifecycle
exports: run
...
Tests: 4/4 (inline + archivo real + parse error + world vacío).
Workspace: 0 errores.
CHANGELOG.md actualizado con la entrada nueva y la del commit
anterior (
|
||
|
|
7b589b863d |
chore: agrega CHANGELOG.md retroactivo
Registro cronológico de los 11 commits previos en la raíz del repo. Cada entrada lista las acciones concretas tras un commit. A partir de este punto, cada cambio sustantivo se documenta también acá en el mismo commit que el código. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
8a83a26de0 |
feat(handshake): notificación push de matches del broker al cliente
El servidor empuja MatchEvent (Available | Lost) a los consumers cuando
sus inputs cambian de match — sea porque un productor llegó, porque
otro mejor lo desplazó, o porque desapareció.
Mecánica:
- Frame::MatchEvent con MatchEventKind { Available, Lost } y los datos
del match (consumer_flow, producer_session/label/flow, ty, via, pinned).
- Server: SessionTxTable (Arc<Mutex<HashMap<SessionId, mpsc::Sender>>>)
+ LastMatches (último match conocido por consumer/input). En cada
register/unregister, broadcast_match_diffs recomputa con el broker
y emite SOLO los diffs respecto al estado anterior.
- Session::run_post_handshake usa tokio::select! para multiplexar
read_frame del cliente y rx.recv() de su tx push.
- Cleanup ahora también limpia push_table y last_matches y dispara un
broadcast (para notificar a quienes pierden el match).
- Client: VecDeque<MatchEvent> bufferea eventos que llegan mezclados
con respuestas a Ping. API:
- take_event() — non-blocking, drena buffer
- await_event(timeout) — bloquea hasta evento o timeout
- ping() ahora drena MatchEvents intermedios hasta encontrar el Pong.
Capacity del canal push por sesión: 32 frames (try_send no-blocking;
si se llena, los eventos extra se descartan — se documenta como
ephemeral, el cliente puede re-consultar via brahman-status).
Test nuevo en brahman-handshake/tests/handshake.rs:
- match_event_pushed_on_producer_arrival: consumer espera, no recibe
evento → llega productor → recibe Available → productor se va →
recibe Lost.
Example nuevo: brahman-handshake/examples/subscriber.rs — cliente que
loguea cada MatchEvent en tiempo real. Útil para ver la dinámica del
broker. Pings cada 25s para keepalive.
Demo end-to-end verificada (4 eventos, 3 ya cubren el ciclo completo):
T+0.3 alpha llega → Available ← demo.alpha.out
T+0.8 beta llega → (sin evento: alpha gana por orden alfabético)
T+1.3 alpha killed → Available ← demo.beta.out (re-evaluación)
T+1.8 beta killed → Lost ← <none>
El broker emite diff: ningún evento cuando un nuevo productor llega
sin desplazar al ganador actual.
Tests: 28/28 (handshake integ 6→7). cargo check --workspace: 0 errores.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
70a7a0d46d |
feat: segundo módulo (nakui) + admin API + brahman-status
Dos cosas en una sesión, en el orden discutido:
(1) Segundo módulo brahman vivo: nakui-core
- crates/modules/nakui/core/Cargo.toml: deps brahman-card,
brahman-sidecar, ulid.
- crates/modules/nakui/core/src/bin/nakui.rs: brahman_card_for_nakui()
construye una Card como Lifecycle::Daemon, Supervision::Restart,
flow.input "command" (json) + flow.output "report" (json). El
cmd_run llama brahman_sidecar::spawn antes de levantar el server
de nakui.
(2) crates/shared/brahman-sidecar (estrena crates/shared/)
Boilerplate del sidecar extraído (DRY): el thread con tokio current
thread runtime, conexión vía Client::connect, ping loop. Yahweh y
nakui ahora consumen este crate. API:
- spawn(card) fire-and-forget
- spawn_with_handle(config) con JoinHandle
Example "presence" útil para demos: módulo dummy con label tomado
del primer arg que se queda vivo hasta SIGTERM.
(3) crates/core/brahman-admin: observabilidad del broker
Socket Unix paralelo en \$BRAHMAN_ADMIN_SOCKET (default
\$XDG_RUNTIME_DIR/brahman-admin.sock). Cada conexión recibe un
StatusSnapshot JSON line-delimited y se cierra. Compatible con nc/socat.
- StatusSnapshot { server, protocol, init_attached, sessions, matches }
- server::AdminServer
- client::query(path)
- example "brahman-status" CLI
(4) Wiring de ente-zero
En primordial_loop, junto al handshake server, ahora también levanta
AdminServer con misma política de degradación grácil.
(5) brahman-broker: BrokeredCard ahora incluye lifecycle. Endpoint y
Match derivan Serialize/Deserialize. Nuevo método cards() expone
iterador de BrokeredCard para que el admin pueda construir snapshots.
(6) brahman-card: re-export pub use ulid::* para que módulos no
necesiten depender de ulid directamente.
(7) yahweh-shell migrado al sidecar compartido. Su brahman_client.rs
pasa de 96 a 53 líneas: sólo declara la Card, delega el spawn.
Demo end-to-end:
$ ente-zero &
$ presence demo.producer &
$ presence demo.consumer &
$ brahman-status
Init: server=0.1.0 protocol=0.1.0 attached=true
Sessions (2):
01KR42TY1J... demo.producer lifecycle=Daemon priority=Normal
01KR42TY1K... demo.consumer lifecycle=Daemon priority=Normal
Matches (2):
demo.producer.in ← demo.consumer.out via Exact
demo.consumer.in ← demo.producer.out via Exact
El broker matchea bidireccional por tipo. El admin lo expone.
Tests: 27/27. cargo check --workspace: 0 errores.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
595f68e252 |
feat(yahweh-shell): primer módulo brahman vivo
yahweh-shell se presenta al Init brahman como módulo Widget mediante un sidecar en thread aparte. La GUI GPUI levanta normalmente; el sidecar mantiene la sesión brahman en paralelo, desacoplado. Cambios: - crates/apps/yahweh-shell/Cargo.toml: deps brahman-card, brahman-handshake, ulid. - crates/apps/yahweh-shell/src/brahman_client.rs: thread con tokio current_thread runtime que arma una Card, llama Client::connect, y loop de pings cada 30s. Si el Init no está disponible, loggea y termina — yahweh sigue funcionando standalone. - crates/apps/yahweh-shell/src/main.rs: brahman_client::spawn() antes de Application::new(). El spawn no bloquea. Card declarada por yahweh: - label: "brahman.ui_engine" - lifecycle: Widget - payload: Virtual (yahweh no se inicia desde el Init, se presenta) - supervision: Delegate - permissions: filesystem read-write (persiste layout.json), IPC wit-v1 - flow.input: render-data (json) - flow.output: user-intent (json) Validación end-to-end: $ ente-zero & $ probe → session=...8G, init_attached=true $ yahweh → [brahman] attached: session=...Y7 Ambos clientes (probe + yahweh sidecar) se registran en el broker del Init en sesiones distintas. yahweh es el primer módulo "real" — no un tester — que vive como nodo del fractal mientras corre. Tests: 27/27 verdes. cargo check --workspace: 0 errores. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
df9d10cc52 |
feat(ente-zero): enchufa el handshake server al Init real
ente-zero (PID 1 del fractal arje) ahora levanta el server de
brahman-handshake junto al ente-bus existente, escuchando en
\$BRAHMAN_INIT_SOCKET (default \$XDG_RUNTIME_DIR/brahman-init.sock).
Es un canal paralelo dedicado a módulos brahman-conscientes que se
presentan con una Card y declaran flujos tipados.
Cambios:
- crates/core/brahman-handshake/src/transport.rs: helper nuevo con
resolución XDG_RUNTIME_DIR → TMPDIR, override por var de entorno
BRAHMAN_INIT_SOCKET. Test unitario para el override.
- crates/core/brahman-handshake/Cargo.toml: example "probe" + dev-dep
anyhow. Probe sirve como herramienta de diagnóstico para conectar
contra cualquier server vivo.
- crates/core/brahman-handshake/examples/probe.rs: cliente mínimo que
hace Hello → Ping → Farewell e imprime el HelloAck recibido.
- crates/core/ente-zero/Cargo.toml: dependencias brahman-handshake
+ brahman-broker.
- crates/core/ente-zero/src/main.rs: en primordial_loop, tras spawn
del ente-bus, crea Arc<Mutex<Broker>> compartido y llama
Server::bind. Si el bind falla (FS no escribible, socket en uso),
loggea y degrada a "modo bus-only" — la doctrina PID 1 no rompe por
subsistemas opcionales (mismo patrón que uevents).
Validación end-to-end manual:
$ BRAHMAN_INIT_SOCKET=/tmp/e2e.sock ./target/debug/ente-zero &
$ BRAHMAN_INIT_SOCKET=/tmp/e2e.sock cargo run --example probe
HelloAck: session=01KR41Q8... server=0.1.0 protocol=0.1.0 init_attached=true
Pong: ts=1778252489714ms
Farewell OK
Tests: 27/27 (broker 11 + card 8 + handshake codec+transport 2 + integ 6).
cargo check --workspace: 0 errores.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
07d77a335f |
feat(handshake): integra el broker con el ciclo de sesiones
ServerConfig acepta un Option<Arc<Mutex<Broker>>> compartido. Cuando está
presente, el servidor lo mantiene en sincronía con las sesiones:
- Tras un Hello aceptado, register_session indexa la Card en el broker
ANTES de insertar en el SessionRegistry y de emitir HelloAck.
- Al cerrar la sesión (Farewell, EOF, o error en run_post_handshake), un
cleanup() unificado llama unregister en el broker y remove en el
SessionRegistry. Garantizado por refactor de Session::handle a
do_handshake → run_post_handshake → cleanup.
Tests nuevos en handshake.rs:
- broker_registers_and_unregisters_with_session: confirma el ciclo
register → farewell → unregister.
- broker_matches_two_live_modules: dos clientes (productor + consumidor)
conectados; el broker resuelve find_producer_for(consumer.session, "in")
→ producer "dht". Tras farewell del productor, el match desaparece.
Fix colateral: brahman-card::TypeRef pasa de internally-tagged
(#[serde(tag = "kind")]) a externally-tagged (default). Postcard no
soporta internally-tagged en formatos no self-describing — sin este
cambio el wire de Hello con Cards que tengan flujos no codificaba.
JSON cambia de {"kind":"primitive","name":"x"} a
{"primitive":{"name":"x"}}. Documentado en el doc-comment de TypeRef.
26/26 tests verdes (broker 11 + card 8 + handshake codec 1 + integ 6).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
5091106c21 |
feat(core): brahman-broker — matching híbrido productor↔consumidor
Crate nuevo en crates/core/brahman-broker que indexa Cards por SessionId
y empareja flow.input de un consumidor con flow.output del productor más
adecuado.
Tres ejes de matching:
1. Estrategia (MatchStrategy):
- Exact: igualdad estricta de TypeRef.
- Structural: misma forma — para Wit, mismo package + name (ignora
interface); para Primitive, mismo name.
- ExactThenStructural (default): prefiere Exact; cae en Structural si
no hay. Reporta cuál ganó en Match.via.
2. Override pin_to: si el consumidor declara pin_to = "label", el broker
prefiere productores con ese label (siempre que el tipo matchee).
Si la pista falla, cae en type-search general. Match.pinned indica
qué camino se siguió.
3. Prioridad: empate de tipo se resuelve por Card.priority (Critical >
High > Normal > Low). Empate de prioridad se resuelve lexicográfica-
mente por label (estable y determinista).
API mínima:
- Broker::new(config)
- register(session, &Card) / unregister(session)
- find_producer_for(consumer_session, input_name) -> Option<Match>
- all_matches() -> Vec<Match> (introspección)
El broker es stateless w.r.t. routes: cada query se computa bajo demanda.
Sólo persiste el índice de BrokeredCard (vista mínima: label, priority,
inputs, outputs).
Cambio aditivo en brahman-card: Priority deriva PartialOrd/Ord/Hash para
ser usable como tiebreaker.
Tests: 11/11.
- exact_match_same_typeref
- structural_ignores_interface
- exact_strategy_rejects_interface_mismatch
- exact_then_structural_prefers_exact
- pin_to_overrides_type_search
- pin_to_unresolvable_falls_back_to_type_match
- priority_breaks_ties
- label_alpha_breaks_priority_ties
- unregister_removes_producer
- no_self_loops
- all_matches_lists_pairs
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
814390feec |
feat(core): brahman-handshake — protocolo runtime Init↔módulo
Crate nuevo en crates/core/brahman-handshake que implementa el handshake real del shared_wit/protocol.wit como wire format Rust↔Rust sobre Unix socket con frames length-prefixed + cuerpo postcard. Componentes: - src/messages.rs: Hello, HelloAck, Ping, Pong, Farewell, HandshakeError y Frame (enum-suma). HandshakeError ya implementa thiserror::Error y cruza el wire. - src/codec.rs: write_frame / read_frame asíncronos con MAX_FRAME_BYTES de 4 MiB. Test interno de roundtrip. - src/server.rs: Server::bind crea el listener en Unix socket; emite ResolvedCard tras validar la Card y devuelve ULID como SessionId. ServerConfig.init_attached se reporta en HelloAck. - src/client.rs: Client::connect hace pre-validación local de la Card (fail fast), envía Hello, parsea HelloAck. ping() y farewell() expuestos. - tests/handshake.rs: 4 tests de integración: * full_handshake_roundtrip — happy path con 3 pings + farewell * rejects_invalid_card_client_side — label vacío rechazado pre-envío * server_rejects_protocol_mismatch — protocol_version 999.0.0 → Error * ping_before_hello_rejected — Ping sin Hello previo → Rejected Limitación conocida: postcard no serializa serde_json::Value (variantes Array/Object con length dinámico). Se removieron por eso los campos `extensions` (Card) y `extra` (Permissions). Forward-compat queda cubierta por schema_version + protocol_version negotiation; si más adelante necesitamos preservar campos JSON desconocidos, irá en un WireCard separado o un envelope. 13/13 tests verdes (brahman-card 8 + brahman-handshake codec 1 + integ 4). cargo check --workspace: 0 errores. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
ed0e973c81 |
refactor(arje): migra ente-card a re-export de brahman-card
ente-card pasa a ser un crate-shim que re-exporta los tipos de brahman-card bajo sus nombres legacy: - EntityCard ≡ brahman_card::Card (alias) - Capability, Payload, SomaSpec, Supervision, etc. — pub use directo Cambios concretos: - crates/core/brahman-card/src/lib.rs: añade impl Default for Card. Permite usar `..Default::default()` en struct-literals para los campos aditivos (permissions, lifecycle, priority, flow, extensions). - crates/core/ente-card/src/lib.rs: reescrito como shim de re-export (~25 líneas). Las definiciones, validaciones y tests viven en brahman-card. - crates/core/ente-card/Cargo.toml: deps reducidas a brahman-card; se eliminan serde/serde_json/ulid (vienen transitivos vía re-export). - crates/core/ente-zero/src/seed.rs: 4 struct-literals de EntityCard ahora terminan con `..Default::default()` para cubrir los nuevos campos del schema híbrido. Los 21 consumidores de ente-card (ente-zero, ente-bus, ente-brain, ente-soma, ente-cas, los 12 *-compat, etc.) compilan sin cambios — sus `use ente_card::EntityCard` y demás imports siguen resolviendo, ahora a tipos de brahman-card. cargo test -p brahman-card: 8/8. cargo build -p ente-zero: OK. cargo check --workspace: 0 errores. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
0feba74503 |
feat(core): brahman-card — Tarjeta de Presentación canónica híbrida
Crate nuevo en crates/core/brahman-card que unifica el modelo de arje y el de brahman/core_protocol original: De arje (ente-card): - Identidad ULID + lineage opcional - Capability tipado (Spawn, Journal, Endpoint, Device, Netlink, ...) - Payload discriminado (Wasm | Native | Virtual | Legacy) - SomaSpec (namespaces, cgroups, rlimits, cpu_affinity) - Supervision (Restart con backoff, OneShot, Delegate) - genesis: Vec<Card> recursivo - Validación exhaustiva (label, self-dep, payload, rlimits, cgroup) Aditivo brahman: - flow: Flows con TypeRef discriminado (Primitive | Wit) - pin_to opcional como pista para el broker - Permissions enumerados (NetworkingPolicy, FsPolicy, IpcPolicy) - Lifecycle ortogonal (Daemon | Oneshot | Widget) - Priority de scheduling - TrustLevel derivado de Permissions (no declarado) - ResolvedCard con WitInterface opcional - extensions: HashMap para forward-compat Formatos: JSON canónico + TOML, auto-detectados por extensión de archivo. Tests: 8/8 incluyendo arje_seed_format_compatible que valida que el formato JSON existente de arje sigue parseando con defaults para los campos nuevos. ente-card original sigue intacto; los consumidores (ente-zero, etc.) migrarán en una pasada posterior. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
4d50bfc587 |
chore: absorbe nakui (ERP matemático) en modules/nakui
- crates/modules/nakui/core/: el crate nakui-core (4 bins, tests).
Deps directas (serde, rhai, surrealdb, petgraph, sha2, uuid, tokio,
thiserror v1) — no convertidas a workspace = true en esta pasada.
- crates/modules/nakui/modules/{inventory,sales,treasury}/: datos
declarativos del dominio (nsmc.json, schema.k, morphisms/) que el
crate consume — no son crates.
cargo check -p nakui-core: 0 errores.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
53dbdf0f1d |
chore: monorepo inicial con arje + minga + yahweh absorbidos
Workspace en 4 ejes (core/modules/apps/shared):
- core/: 24 crates de arje (Init systemd-compatible: ente-card, ente-zero,
ente-kernel, ente-bus, ente-cas, ente-soma, ente-wasm, ente-snapshot,
ente-brain, ente-echo, ente-policy-provider, + 12 crates *-compat)
- modules/semantic_dht/: 5 crates de minga (minga-core con AST/CAS/MST,
minga-p2p con libp2p Kad, minga-store, minga-vfs, minga-cli)
- modules/ui_engine/: 11 crates de yahweh (libs/{core,theme,bus,providers},
widgets/{tree,splitter,tabs,tiled,container_core,text_input})
- apps/: 5 crates de yahweh (file_explorer, database_explorer, text_viewer,
image_viewer, yahweh-shell)
- shared_wit/protocol.wit: handshake/lifecycle inicial
Cargo.toml unificado: thiserror bumped a 2 (transparente para arje), tokio
"full", paths intra-workspace de yahweh redirigidos a su nueva ubicación.
cargo check --workspace: 0 errores, 17 warnings (dead code preexistente).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|