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>
This commit is contained in:
Sergio
2026-05-08 19:38:23 +00:00
parent 794884a90f
commit 5edc912ed8
9 changed files with 134 additions and 23 deletions
+14 -1
View File
@@ -19,7 +19,7 @@
#![warn(rust_2018_idioms)]
use std::collections::{BTreeMap, BTreeSet, HashSet};
use std::path::Path;
use std::path::{Path, PathBuf};
use std::time::Duration;
use serde::{Deserialize, Serialize};
@@ -126,6 +126,14 @@ pub struct Card {
#[serde(default)]
pub flow: Flows,
/// Si la entidad expone un socket Unix de servicio (data plane,
/// distinto al socket del Init), declara aquí su path. Los
/// consumidores que reciban un `MatchEvent` con este Card como
/// productor pueden conectar directo al socket sin discovery
/// adicional.
#[serde(default)]
pub service_socket: Option<PathBuf>,
/// Naturaleza de la entidad detrás de la Card. Por defecto `Ente`
/// para mantener compatibilidad con Cards existentes.
#[serde(default)]
@@ -176,6 +184,7 @@ impl Default for Card {
priority: Priority::default(),
flow: Flows::default(),
genesis: Vec::new(),
service_socket: None,
kind: CardKind::default(),
data: None,
priority_contexts: BTreeMap::new(),
@@ -803,6 +812,8 @@ pub struct WireCard {
#[serde(default)]
pub genesis: Vec<WireCard>,
#[serde(default)]
pub service_socket: Option<PathBuf>,
#[serde(default)]
pub kind: CardKind,
#[serde(default)]
pub data: Option<DataFacet>,
@@ -827,6 +838,7 @@ impl From<Card> for WireCard {
priority: c.priority,
flow: c.flow,
genesis: c.genesis.into_iter().map(WireCard::from).collect(),
service_socket: c.service_socket,
kind: c.kind,
data: c.data,
priority_contexts: c.priority_contexts,
@@ -851,6 +863,7 @@ impl From<WireCard> for Card {
priority: w.priority,
flow: w.flow,
genesis: w.genesis.into_iter().map(Card::from).collect(),
service_socket: w.service_socket,
kind: w.kind,
data: w.data,
priority_contexts: w.priority_contexts,