Jesús Pérez d59644b96f
feat: unified auth model, project onboarding, install pipeline, config management
The full scope across this batch: POST /sessions key→token exchange, SessionStore dual-index with revoke_by_id, CLI Bearer injection (ONTOREF_TOKEN), ontoref setup
  --gen-keys, install scripts, daemon config form roundtrip, ADR-004/005, on+re self-description update (fully-self-described), and landing page refresh.
2026-03-13 20:56:31 +00:00

33 KiB

theme, title, titleTemplate, layout, keywords, download, exportFilename, monaco, remoteAssets, selectable, record, colorSchema, lineNumbers, themeConfig, fonts, drawings, scripts, class
theme title titleTemplate layout keywords download exportFilename monaco remoteAssets selectable record colorSchema lineNumbers themeConfig fonts drawings scripts class
default Ontology and Reflection %s - OntoRef cover Nickel,Nushell,Ontology,Reflection,StratumIOps true OntoRef true true true true dark false
primary logoHeader
#f74c00 /ferris.svg
mono
Victor Mono
enabled persist presenterOnly syncAll
true false false true
setup/image-overlay.ts
justify-center flex flex-cols

Ontology & Reflection

Typing the invisible. Verifying the obvious. Sealing what matters.

layout: two-cols

¿Qué estamos resolviendo?

Los problemas

🔴
Epistemic drift
El código hace X, el equipo cree que hace Y.
Silencioso. Acumulativo.
🔴
Multi-actor collision
Humano + agente IA escriben al mismo fichero,
sin coordinación.
🟠
Decision amnesia
¿Por qué se tomó esta decisión? ¿Quién la autorizó?
🟠
Config schizophrenia
Staging funciona, prod no. ¿Qué campo cambió y cuándo?

::right::

🟠
Docs como artefactos muertos
Escritas una vez, olvidadas, nunca actualizadas.
🟡
Agent con ceguera
Los agentes IA empiezan sin contexto,
violan invariantes establecidas.

Ejemplo: desincronización real

# Documentación (2024-01)
config.max_retries = 3

# Código actual (después de cambio sin registrar)
config.max_retries = 7

# Equipo cree que está usando 3 ✗
# Sistema usa 7 silenciosamente ✓
# Nadie sabe cuándo cambió ni por qué

layout: default

El coste de ignorarlo

Deuda técnica      → arreglable
Deuda epistémica   → sistémica
Deuda de confianza → letal
Técnica
Refactoriza, reescribe, corrige.
1-3 sprints
Epistémica
Acumula silenciosamente. No sabes dónde.
Crece cada semana
Confianza
Se disuelve cuando se descubre el drift.
Imposible reparar rápido

Escenario: 6 meses de ignorar

Mes 1: "Solo refactorizar esto"
Mes 2: "Documentación está desactualizada"
Mes 3: "¿Por qué prod falla y staging no?"
Mes 4: "Nadie sabe quién cambió qué"
Mes 5: Pánico. Rollback arriesgado.
Mes 6: Equipo rechaza cambios nuevos.
         "No confiamos sin entender"
Estos problemas no tienen solución técnica.
Tienen solución de arquitectura.

layout: default

Verdad y operación — dos fuerzas que deben coexistir

Yin — la capa formal

Lo que debe ser verdad

  • Nickel schemas → corrección estructural
    en tiempo de definición
  • ADR constraints → "esto nunca puede violarse"
  • Config seals → estados sellados, verificables con sha256
  • Ontology invariants → lo que no puede cambiar
    sin un nuevo ADR
  • Hashes matemáticos → prueba de qué fue sellado
    y cuándo

Yang — la capa operacional

Cómo las cosas se mueven y cambian

  • Nu commands → transformación estructurada de datos
  • Actors (human/agent/CI) → mismo protocolo,
    distintas capacidades
  • Register flow → captura cambios,
    los enruta al artefacto correcto
  • Mode definitions → secuencias de operaciones
    con verificación
  • Pre-commit hooks → sincronización forzada
    en el momento del commit
Yang sin Yin  = fluido pero caótico.  Cualquier cosa puede cambiar.       Nada es verificable.
Yin  sin Yang = correcto pero inútil. Schemas perfectos sin operaciones = documentación muerta.
El sistema vive en la coexistencia.

layout: center

Los ingredientes

CAPA DECLARATIVA  ·  Nickel
.ontology/  ·  adrs/  ·  reflection/schemas/  ·  reflection/configs/
Tipos fuertes, contratos, enums. Falla en definición, no en runtime.
CAPA OPERACIONAL  ·  Nushell
adr  ·  register  ·  config  ·  backlog  ·  forms  ·  prereqs
Pipelines tipadas sobre datos estructurados. No streams de texto.
PUNTO DE ENTRADA  ·  Bash → Nu
stratum.sh  ·  actor detection  ·  locking  ·  NICKEL_IMPORT_PATH
Un único entry point. Detecta actor, adquiere lock, despacha al módulo Nu correcto.
GRAFO DE CONOCIMIENTO  ·  Ontología + ADRs
nodes  ·  invariants  ·  gates  ·  dimensions  ·  states
El sistema sabe qué sabe. Actor-agnostic. Machine-queryable.
ESTADOS SELLADOS  ·  Config + Historia
profiles  ·  sha256 seals  ·  audit trail  ·  rollback
Inmutabilidad verificable. Drift detection. Trazabilidad completa ADR/PR/bug.

layout: default

Nickel — configuración con tipos, sin runtime

Qué es Nickel

  • Lenguaje de configuración con tipos fuertes (de Tweag)
  • Puramente declarativo: sin efectos secundarios, sin loops, sin runtime
  • Superset de JSON: JSON válido es Nickel válido
  • Imports: let s = import "schema.ncl" in
  • Contratos: field | TypeName — verificado al merge
  • Enums tipados: [| 'Proposed, 'Accepted, 'Superseded |]
  • Registros abiertos/cerrados:
    { _: String } vs { field | String }
  • nickel export file.ncl → JSON estable pipelines Nu
El sistema de tipos es la documentación.

Por qué no alternativas

YAML  → sin tipos, whitespace-sensitive,
        coerciones implícitas
TOML  → sin tipos ≥ primitivos, sin imports
JSON  → sin comentarios, sin imports, sin tipos
HCL   → efectos secundarios, no es config pura
CUE   → similar pero peor historia de imports
KCL   → tipos + validación, pero vendedor
        lenguaje opaco
Por qué aquí específicamente
  • ADR constraints necesitan typecheck, no validación manual
  • Perfiles de config deben fallar en definición, no en runtime
  • nickel export produce JSON estable para consumo Nu
# YAML: silently accepts garbage
actor: developr   # typo, accepted
# Nickel: rejected at typecheck
{ actor | [| 'developer, 'agent, 'ci |] = "developr" }
#                       ^^^^^^^^^
#                error: tag not in enum type

layout: default

Nushell — datos estructurados, no streams de texto

Shell tradicional: texto opaco

# Bash: parsear texto, error-prone
grep 'severity' adrs/adr-001.json |\
    python3 -c "import sys,json; ..."
Nushell: records nativos
nickel export adrs/adr-001.ncl
  | from json
  | get constraints
  | where severity == "Hard"
  | select id claim
╭───┬────────────┬──────────────────────────────╮
│ # │ id         │ claim                        │
├───┼────────────┼──────────────────────────────┤
│ 0 │ adr-001-c1 │ nickel export must succeed…  │
│ 1 │ adr-001-c2 │ schema is sole source of…    │
╰───┴────────────┴──────────────────────────────╯

Nickel → Nu → Bash — tres capas, tres roles

Nickel exporta JSON tipado
Nunca strings mágicos. Contratos en la definición, no en runtime.
Nu consume estructuras
Tables, records, pipelines. Filter, sort, merge — sin awk ni sed.
Bash orquesta procesos
Locking, signals, exec. Solo lo que bash hace de forma nativa.
# Constraints Hard del sistema completo
glob "adrs/adr-*.ncl"
| each { |f| nickel export $f | complete | get stdout }
| each { from json }
| where status == "Accepted"
| get constraints | flatten
| where severity == "Hard"

layout: default

DAGs y FSMs — grafos acíclicos y máquinas de estado

FSM: ciclo de vida de un ADR

  ┌──────────┐  adr accept  ┌──────────┐
  │ Proposed │ ───────────▶ │ Accepted │
  └──────────┘              └──────────┘
                                  │ new ADR
                                  │ supersedes
                                  ▼
                            ┌────────────┐
                            │ Superseded │
                            └────────────┘
Transiciones typecheck-enforced en Nickel:
status | [| 'Proposed, 'Accepted, 'Superseded |]
superseded_by | String | default = ""
Las transiciones inválidas no existen
— el tipo las elimina del espacio de posibilidades.
Un ADR no puede volver a Proposed.
Superseded no puede tener constraints activas.

DAG: ontología como grafo de dependencias

invariante: backend-agnostic-core
  └─ tensión: nickel-as-canonical-schema
       └─ gate: nickel-primacy-gate [activa]
            └─ protege: nickel-integration-depth

invariante: zero-external-runtime-core
  └─ gate: core-dependency-gate [activa]
       └─ protege: backend-agnostic-core
Por qué importa la aciclicidad
→ Un invariante no puede depender de sí mismo
→ Las gates protegen nodos hoja, no ciclos
→ El rollback es determinista: sin ambigüedad de orden
# Invariantes activos
nickel export .ontology/core.ncl
| from json | get nodos
| where invariante == true
| select id descripcion

layout: default

La Ontología Operacional — el grafo operacional

core.ncl — lo que no puede cambiar

# .ontology/core.ncl
{
  nodos = [
    {
      id          = "backend-agnostic-core",
      invariante  = true,
      descripcion = "business logic sin deps de UI",
      tensiones   = ["nickel-as-canonical-schema"],
    },
  ],
  practicas = [...],
}
Tocar un nodo con invariante = true requiere un nuevo ADR. Sin excepción.

state.ncl y gate.ncl — dónde estamos

nickel export .ontology/state.ncl
| from json | get dimensiones
| select id estado_actual estado_deseado
backend-maturity         multi-stable  → all-production
nickel-integration-depth schema-input  → bidirectional
nickel export .ontology/gate.ncl
| from json | get membranas
| where activa == true
| select id permeabilidad protege
core-dependency-gate  Low  [backend-agnostic-core, ...]
nickel-primacy-gate   Low  [nickel-as-canonical-schema]
La ontología no es un diagrama.
Es un grafo consultable que el sistema y los agentes leen.

layout: default

Reflection — modos, formas, módulos, perfiles

Modo: especificación ejecutable

# reflection/modes/new_service.ncl
{
  id      = "new_service",
  trigger = "When adding a service",
  steps   = [
    {
      id       = "create-adr",
      actor    = "developer",
      action   = "Draft ADR for service boundary",
      cmd      = "stratum form new_adr",
      on_error = { strategy = "abort" },
    },
    {
      id         = "seal-config",
      actor      = "developer",
      action     = "Apply initial config seal",
      cmd        = "stratum config apply development",
      depends_on = [{ step = "create-adr" }],
      on_error   = { strategy = "abort" },
    },
  ],
}

Forma: entrada estructurada tipada

# reflection/forms/register_change.ncl
{
  elements = [
    { type = "section_header", label = "Change" },
    {
      type     = "text",
      id       = "title",
      label    = "Title",
      required = true,
    },
    {
      type    = "select",
      id      = "change_type",
      options = ["feature","fix","refactor","config"],
    },
  ]
}
Perfil de config = estado sellado
sha256(nickel export profile.ncl) — drift detection instantánea
stratum config verify development → compara hash actual con hash sellado

layout: default

ADRs vivos

— de documento muerto a constraint machine

Estructura Nickel de un ADR

# adrs/adr-001-nickel-as-canonical.ncl
let s = import "adrs/schema.ncl" in
{
  id      = "adr-001",
  title   = "Nickel as First-Class Form Definition",
  status  = "Accepted",
  date    = "2025-12-01",
  constraints = [
    {
      id       = "adr-001-c1",
      severity = "Hard",
      claim    =
        "nickel export must succeed before "
         ++ "any user interaction",
    },
  ],
  superseded_by = "",
} | s.ADR

Ciclo de vida y consulta

Proposed ──────▶ Accepted ──────▶ Superseded
  │                                   ▲
  │            new ADR                │
  └──────── superseded_by ────────────┘
Constraints Hard activas del sistema
glob "adrs/adr-*.ncl"
| each { |f|
    nickel export $f | complete
    | if $in.exit_code == 0 { 
        $in.stdout | from json 
    } else { null }
  }
| compact
| where status == "Accepted"
| get constraints | flatten
| where severity == "Hard"
| select id claim
El conjunto activo de constraints es el contrato en vigor — no la prosa del ADR. La prosa explica el porqué. Los constraints enforcan el qué.

layout: default

Implementación — las partes y cómo encajan

Stack de archivos operativos

stratum.sh                 ← entry point, locking, env
  └─ reflection/bin/stratum.nu  ← dispatcher
       ├─ modules/adr.nu        ← ADR lifecycle
       ├─ modules/register.nu   ← CHANGELOG + ontology
       ├─ modules/config.nu     ← sealed profiles
       ├─ modules/backlog.nu    ← backlog items
       └─ modules/forms.nu      ← TypeDialog integrated

.ontology/                 ← queryable truth
  ├─ core.ncl              ← invariants
  ├─ state.ncl             ← dimensions
  └─ gate.ncl              ← active guards

adrs/                      ← typed decisions
reflection/configs/        ← sealed config history

Flujo de escritura protegida

stratum.sh
detect actor → acquire lock (mkdir) → delegate → release
Lock resources
manifest (config apply/rollback) · changelog (register) · backlog (done/cancel)
Timestamp IDs
cfg-20260309T045206-developer — colisión imposible, sin TOCTOU
# Advisory lock: mkdir es POSIX-atómico
mkdir .stratumiops/locks/manifest.lock 2>/dev/null
echo "$$:developer:$(date -u +%Y%m%dT%H%M%SZ)" 
    > .stratumiops/locks/manifest.lock/owner

layout: default

Mecánica y flujo — casos de uso reales

Flujo: registrar un cambio

stratum register → forma TypeDialog interactiva
Clasificar: tipo de cambio, impacto, ADR relacionado
Artefacto: entrada CHANGELOG + stub ADR si aplica
Verificar: nickel typecheck debe pasar
Sellar si afecta config: stratum config apply
Detección de drift
stratum config apply production --adr adr-006 --pr 42
# Meses después:
stratum config verify production
# → ✓ verified  a3f7c12d8b4e9f01...
# → ✗ DRIFT DETECTED stored vs current hash

Rollback verificado

stratum config history production

stratum config rollback production \
    cfg-20260301T120000-developer  \
    --adr adr-007 \
    --note "revert breaking change"
snapshot_hash verifica integridad antes de restaurar
→ Rollback genera nuevo sello — la historia es append-only
→ Trazabilidad completa: ADR + PR + bug en cada sello
cfg-20260301T120000-developer  (original)
  └─ cfg-20260309T045206-agent  (change)
       └─ cfg-20260309T120000-developer  (rollback)

layout: default

Integración en proyectos — onboarding al ecosistema

Lo que un proyecto necesita

proyecto/
  .ontology/
    core.ncl       ← invariants del proyecto
    state.ncl      ← dimensiones de madurez
    gate.ncl       ← guards activos
  adrs/
    schema.ncl
    reflection.ncl
    adr-001-*.ncl  ← ADR fundacional
  reflection/
    forms/         ← TypeDialog forms
    modes/         ← operational modes
    configs/       ← sealed profiles
    modules/       ← Nu modules
    bin/stratum.nu
  stratum.sh

Inicialización

Comando de onboarding
/onboard-project → genera .ontology/
+ ADR fundacional
Primer ADR obligatorio
Define las invariantes — qué no puede cambiar sin ADR nuevo
Pre-commit como sincronización
nickel typecheck en cada commit
— la capa declarativa nunca queda rota
./stratum.sh check
# ✓ nushell 0.110.0
# ✓ nickel 1.9.0
# ✓ .ontology/core.ncl exportable
# ✓ adrs/adr-001 Accepted
# ✗ no sealed config profiles yet

layout: default

Integración con Claude Code & Uso concurrente

SessionStart: contexto automático

stratum-session-start.sh se ejecuta al inicio de cada sesión:
=== STRATUM CONTEXT: typedialog ===

ADRS
  adr-001 [Accepted] Nickel as First-Class Form...
  adr-002 [Accepted] NCL element order from position

HARD CONSTRAINTS
  adr-001-c1  nickel export must succeed before...
  adr-002-c1  web/tui must render headers interleaved

STATE
  backend-maturity: multi-stable → all-production

ACTIVE GATES
  nickel-primacy-gate [Low] Protects: nickel-as-...

=== END STRATUM CONTEXT ===

Uso concurrente: humano + agente IA

IDs timestamp con actor
cfg-20260309T045206-developer
cfg-20260309T045207-agent
Sin coordinador. Sin TOCTOU. Trazabilidad por actor.
Advisory locks (mkdir)
Serializa escrituras en manifest/changelog/backlog. Stale detection por PID.
Actor-agnostic protocol
El sistema no distingue humano de agente. El protocolo enforcea igual para ambos.
Worktrees aíslan el working-tree (branches diferentes).
Locking serializa escrituras sobre el mismo árbol.
Son complementarios, no alternativos.

layout: cover name: end class: 'justify-center flex flex-cols'

Ontology & Reflection

✓ Typed ✓ Verified ✓ Sealed ✓ Decided
El código y el entendimiento del código deben converger.
Los ADRs y la ontología son el contrato entre ambos.
La reflection es la operación sobre ese contrato.
Gracias. ¿Preguntas?

jesusperez.pro · ontoref.dev