2026-09-19 — TypeSafe: usos factibles + lab v2 con gate anti-dato-sensible

Contexto: cierre de la sesión signal-only (~/typesafe-sandbox). El OpenAPI real revela que /v1/systemone soporta 3 tipos de pregunta (noul, score, choice) y que state admite texto completo. Uso seguro bajo políticas leoninas: TypeSafe solo ve datos públicos o abstractos; lo sensible se procesa en local.

Contrato corregido (error de la sesión anterior)

state = CONTENIDO a evaluar. questions.*.instructions = la decisión a tomar. El typesafe-prompt viejo enviaba state: {} — por eso el experimento del HTML devolvió 0.61 sin sentido: no había contenido que evaluar.

Qué puede cruzar la frontera (y qué no)

SALE (state允许的)NUNCA SALE
Titulares/resúmenes de noticias publicadasNombres de clientes, marcas, proyectos WP
Features numéricos ya agregados (z-scores, ratios)PII: emails, teléfonos, URLs internas
Descripción genérica de una idea (sin nombres/cifras)Corpus ai-sessions/, notas del segundo cerebro
Texto sintético de pruebaCredenciales, tokens, contenido del .env

El gate _ts_gate bloquea el envío si el stdin huele a email/URL/clave/dígitos largos. Si hay que abstrair: primero Ollama (reescritura genérica en ~30 palabras), luego TypeSafe.

Primitivas (bloque verbatim ya instalado en ~/.zshrc, tras typesafe-prompt)

# ---- TypeSafe lab v2: state + noul/score/choice, con gate anti-dato-sensible ----
# Credenciales: locals dentro de cada funcion, leidas de .env; nunca exportadas al shell.
 
_ts_creds() {
  local env_file="~//typesafe-sandbox/.env"
  [[ -f "$env_file" ]] || { echo "ERROR: no existe $env_file" >&2; return 1 }
  local u k
  u="$(grep '^TYPESAFE_URL=' "$env_file" | head -n1 | cut -d= -f2-)"
  k="$(grep '^TYPESAFE_API_KEY=' "$env_file" | head -n1 | cut -d= -f2-)"
  [[ -n "$u" && -n "$k" ]] || { echo "ERROR: faltan TYPESAFE_URL o TYPESAFE_API_KEY en .env" >&2; return 1 }
  print -r -- "$u"
  print -r -- "$k"
}
 
_ts_gate() {
  # Filtro por stdin: huele a email/URL/clave/numero largo -> bloquea el envio
  local blob; blob="$(cat)"
  if grep -qiE '[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}|https?://[a-z0-9]|(api[_-]?key|token|secret|password|passphrase)[^a-z]|([0-9][ -/]?){10,}' <<<"$blob"; then
    echo "BLOCKED: el state contiene algo con pinta de dato sensible. Abstrae o localiza antes de enviar." >&2
    return 1
  fi
  print -r -- "$blob"
}
 
_ts_load() {
  # sets globals _TS_URL/_TS_KEY; use inside functions that declare them local
  local creds
  creds="$(_ts_creds)" || return 1
  _TS_URL="${creds%%$'\n'*}"
  _TS_KEY="${creds#*$'\n'}"
  [[ -n "$_TS_URL" && -n "$_TS_KEY" ]]
}
 
_ts_post() {
  # stdin = payload JSON ya construido
  local _TS_URL _TS_KEY payload
  _ts_load || return 1
  payload="$(cat)"
  curl -sS "$_TS_URL" \
    -H "Authorization: Bearer $_TS_KEY" \
    -H "Content-Type: application/json" \
    -d "$payload" | jq '{answers, usage}'
}
 
typesafe-noul() {
  # echo "contenido" | typesafe-noul "¿pregunta si/no?" ["lo que cuenta como si" "lo que cuenta como no"]
  local q="$1" t="$2" f="$3"
  local state payload
  [[ -n "$q" ]] || { echo "usa: typesafe-noul 'pregunta'" >&2; return 1 }
  state="$(_ts_gate)" || return 1
  [[ -n "$state" ]] || { echo "ERROR: state vacio por stdin" >&2; return 1 }
  if [[ -n "$t" ]]; then
    payload="$(jq -n --arg s "$state" --arg q "$q" --arg t "$t" --arg f "$f" \
      '{state:$s, model:"jev-latest", questions:{a:{type:"noul", instructions:$q, criteria:{true:$t, false:$f}}}}')"
  else
    payload="$(jq -n --arg s "$state" --arg q "$q" \
      '{state:$s, model:"jev-latest", questions:{a:{type:"noul", instructions:$q}}}')"
  fi
  print -r -- "$payload" | _ts_post
}
 
typesafe-choice() {
  # echo "contenido" | typesafe-choice "instruccion" opcion1 opcion2 ...
  local instr="$1"; shift
  local state payload crit o
  [[ -n "$instr" ]] || { echo "usa: typesafe-choice 'instruccion' opcion1 opcion2 ..." >&2; return 1 }
  (( $# )) || { echo "usa: typesafe-choice 'instruccion' opcion1 opcion2 ..." >&2; return 1 }
  state="$(_ts_gate)" || return 1
  [[ -n "$state" ]] || { echo "ERROR: state vacio por stdin" >&2; return 1 }
  crit='{}'
  for o in "$@"; do crit="$(jq -c --arg k "$o" '. + {($k): $k}' <<<"$crit")"; done
  payload="$(jq -n --arg s "$state" --arg i "$instr" --argjson c "$crit" \
    '{state:$s, model:"jev-latest", questions:{a:{type:"choice", instructions:$i, criteria:$c}}}')"
  print -r -- "$payload" | _ts_post
}
 
typesafe-score() {
  # echo "contenido" | typesafe-score "instruccion" nivel0 nivel1 nivel2 ...
  local instr="$1"; shift
  local state payload crit
  [[ -n "$instr" ]] || { echo "usa: typesafe-score 'instruccion' nivel0 nivel1 ..." >&2; return 1 }
  (( $# )) || { echo "usa: typesafe-score 'instruccion' nivel0 nivel1 ..." >&2; return 1 }
  state="$(_ts_gate)" || return 1
  [[ -n "$state" ]] || { echo "ERROR: state vacio por stdin" >&2; return 1 }
  crit="$(jq -cn '$ARGS.positional' --args "$@")"
  payload="$(jq -n --arg s "$state" --arg i "$instr" --argjson c "$crit" \
    '{state:$s, model:"jev-latest", questions:{a:{type:"score", instructions:$i, criteria:$c}}}')"
  print -r -- "$payload" | _ts_post
}
 
typesafe-triage() {
  # echo "resumen generico de la idea (sin nombres, marcas, cifras)" | typesafe-triage
  local state payload
  state="$(_ts_gate)" || return 1
  [[ -n "$state" ]] || { echo "ERROR: state vacio por stdin" >&2; return 1 }
  payload="$(jq -n --arg s "$state" '{
    state: $s,
    model: "jev-latest",
    questions: {
      factible:    {type:"noul",  instructions:"Can one designer-engineer ship a working version in two weekends?"},
      novedoso:    {type:"noul",  instructions:"Is this meaningfully different from tools that already exist?"},
      valor:       {type:"score", instructions:"Value as portfolio or research work", criteria:["none", "some", "strong", "signature"]},
      complejidad: {type:"score", instructions:"Implementation complexity", criteria:["trivial", "moderate", "heavy", "research-grade"]},
      datos:       {type:"noul",  instructions:"Does the data this needs exist locally or as free public data?"}
    }}')"
  print -r -- "$payload" | _ts_post | jq '.answers | {factible:.factible.noul, novedoso:.novedoso.noul, valor:.valor.score, complejidad:.complejidad.score, datos:.datos.noul}'
}
 
typesafe-models() {
  local _TS_URL _TS_KEY base
  _ts_load || return 1
  base="${_TS_URL%/v1/systemone}"
  curl -sS "$base/v1/models" -H "Authorization: Bearer $_TS_KEY" | jq .
}

Recetas de incorporación (verificadas live 2026-09-19)

  1. nef-silo — árbitro de pares borde (mejora 8: silos por fuente, no por evento). Titulares públicos → sí cruzan la frontera. Calibrar epsilon del HDBSCAN:

    printf 'TITULAR A\nTITULAR B' | typesafe-noul \
      "Do these headlines describe the same real-world event?" "same event" "different events"

    Usar como juez en los pares con similitud coseno cerca del umbral (no para todo el corpus).

  2. nef-silo / x-anti-fomo — score de urgencia para filtrar el home.json:

    typesafe-score "How urgent is this for a solo researcher?" "can wait" "this week" "today" <<< "$resumen"

    confidence baja (<0.4) → revisar humano; el score viene con probabilidades por nivel.

  3. nef-silo — clasificación de items fuera de silo (ruido HDBSCAN label -1):

    typesafe-choice "What topic is this?" "IA" "Seguridad" "Infra" "Datos" "Prensa" <<< "$titulo"

    (Probado: choice con confidence 1.0 en un titular AI.)

  4. Triage de ideas sin filtrar el plan — el patrón completo es:

    idea sensible → Ollama: "reescribe en 30 palabras genericas, sin nombres ni cifras"
                  → typesafe-triage → {factible, novedoso, valor, complejidad, datos}
                  → solo las que pasan van a Claude/Qwen para generar artifact

    TypeSafe decide, el generativo crea, el código ejecuta, el humano gobierna.

  5. Salud del silo-engine: noul sobre el log de corrida (“Did this run fail to fetch any source?“) — solo si el log no trae hostnames/creds.

Backlog del gateway (no abrir signal-only hoy; registrar para la v2)

  • Registry question_id → esquema de pregunta con los 3 tipos (hoy solo noul fijo).
  • Validar en el gateway que features sea solo numérico (ya) + tamaño máx. de state.
  • Mantener fingerprint SHA-256 del outbound (auditoría sin payload).

Pendientes de seguridad (bloquean uso en serio)

  1. ROTAR TYPESAFE_API_KEY — se imprimió en claro dos veces (docker inspect en la sesión previa; fallo de una funcion zsh hoy). Rotar = editar .env; las funciones lab no exportan nada al shell (verificado: NO_DEFINIDA).
  2. Rotar GATEWAY_TOKEN.
  3. docker inspect typesafe-blackbox solo con --format selectivo, nunca completo.

Verificación de esta sesión

  • typesafe-noul/choice/score/triage → respuestas estructuradas correctas (jev-1.13.0).
  • Gate: email en el state → BLOCKED sin llamada saliente.
  • No hay TYPESAFE_* ni _TS_KEY definidos en el shell tras usar las funciones.