From d59644b96f312103cf6274f85aaff54b89168e3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jesu=CC=81s=20Pe=CC=81rez?= Date: Fri, 13 Mar 2026 20:56:31 +0000 Subject: [PATCH] feat: unified auth model, project onboarding, install pipeline, config management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .ontology/core.ncl | 80 +- .ontology/manifest.ncl | 4 +- .ontology/state.ncl | 49 +- .../.typedialog}/ci/README.md | 4 +- .../.typedialog}/ci/config.ncl | 0 .../ci/config.ncl.20260312_005551.bak | 0 .../.typedialog}/ci/configure.sh | 0 .../.typedialog}/ci/envrc | 0 .../.typedialog}/ci/form.toml | 0 .ontoref/README.md | 43 - .ontoref/config.ncl | 4 +- .ontoref/gen-registry.nu | 35 - .ontoref/logs | 41 - .ontoref/project.ncl | 8 + .ontoref/registry.ncl | 33 - .ontoref/registry.toml | 14 - CHANGELOG.md | 188 +- Cargo.lock | 3 + README.md | 54 +- adrs/adr-004-ncl-pipe-bootstrap-pattern.ncl | 113 + adrs/adr-005-unified-auth-session-model.ncl | 126 + assets/logo_prompt.md | 119 + assets/presentation/.gitignore | 23 + .../presentation/.tmp-check-slidev-count.mjs | 11 + assets/presentation/.tmp-critical-blocks.mjs | 94 + .../.tmp-regenerate-reader-docs.mjs | 199 + assets/presentation/00_talk-structure.md | 215 + assets/presentation/01_talk-structure.md | 534 + assets/presentation/2026-02-17-notas_voz.md | 101 + .../presentation/2026-02-27-notas_voz_rust.md | 39 + assets/presentation/QUICKSTART.md | 109 + assets/presentation/README.md | 114 + assets/presentation/TO_CHANGE.md | 86 + assets/presentation/_slides.md | 117 + assets/presentation/abstract_en.md | 22 + assets/presentation/ajusta_texto.md | 11 + assets/presentation/components/Footer.vue | 198 + assets/presentation/components/README.md | 382 + .../presentation/components/YearsCounter.vue | 154 + .../critical-phrases-blocks-30min.md | 142 + assets/presentation/fix_slides.md | 1116 ++ assets/presentation/images/arch-diag-v2.svg | 700 + .../blackieshoot-fuR0Iwu5dkk-unsplash.jpg | Bin 0 -> 445953 bytes .../charles-assuncao-1BbOtIqx21I-unsplash.jpg | Bin 0 -> 1703847 bytes .../images/cleo-heck-1-l3ds6xcVI-unsplash.jpg | Bin 0 -> 2391496 bytes .../images/jon-tyson-a4Cm6EnYtls-unsplash.jpg | Bin 0 -> 864690 bytes .../images/kenny-Gx1raEg_3Zw-unsplash.jpg | Bin 0 -> 7972628 bytes .../sincerely-media-CWL6tTDN31w-unsplash.jpg | Bin 0 -> 1095642 bytes assets/presentation/images/w-arch-diag-v2.svg | 703 + .../presentation/key-moments-storytelling.md | 47 + assets/presentation/last_slides.md | 1116 ++ assets/presentation/org_slides.md | 489 + assets/presentation/package.json | 20 + assets/presentation/public/3XXSKa4jKaM.webp | Bin 0 -> 315592 bytes assets/presentation/public/Rust-mascot.png | Bin 0 -> 46286 bytes .../andrew-neel-jtsW--Z6bFw-unsplash.jpg | Bin 0 -> 570686 bytes assets/presentation/public/arch-diag-v2.svg | 700 + assets/presentation/public/clouds_lpa.jpg | Bin 0 -> 55041 bytes assets/presentation/public/crate_in_wall.jpg | Bin 0 -> 83551 bytes .../public/enyesque-rust-laspalmas.svg | 15903 ++++++++++++++++ assets/presentation/public/failed.png | Bin 0 -> 214496 bytes .../public/ferris-celebration.svg | 36 + assets/presentation/public/ferris-heart.svg | 21 + assets/presentation/public/ferris.svg | 33 + ...ethi-bouhaouchine-sTZ_pR8Nns0-unsplash.jpg | Bin 0 -> 232219 bytes .../public/humildad_y_sabiduria_socrates.jpg | Bin 0 -> 769283 bytes .../public/inovacion_y_cambio_einstein.jpg | Bin 0 -> 271057 bytes assets/presentation/public/jesusperez.png | Bin 0 -> 4656 bytes assets/presentation/public/jesusperez_w.png | Bin 0 -> 4656 bytes assets/presentation/public/jesusperez_w.svg | 35 + assets/presentation/public/jpl-image.svg | 402 + assets/presentation/public/jpl-imago-160.png | Bin 0 -> 24649 bytes assets/presentation/public/jpl-imago-320.png | Bin 0 -> 63878 bytes .../presentation/public/jpl-imago-static.svg | 151 + .../public/jpl-imago-wallpaper-4k.png | Bin 0 -> 824435 bytes .../public/jr-korpa-vg0Mph2RmI4-unsplash.jpg | Bin 0 -> 524410 bytes .../jude-infantini-mI-QcAP95Ok-unsplash.jpg | Bin 0 -> 1286051 bytes assets/presentation/public/mYBMP8pW4uQ.webp | Bin 0 -> 572076 bytes assets/presentation/public/ontoref-logo.svg | 193 + assets/presentation/public/ontoref-text.svg | 19 + assets/presentation/public/ontoref_img.svg | 197 + assets/presentation/public/ontoref_text.svg | 19 + assets/presentation/public/provisioning.svg | 161 + .../presentation/public/qr-code-encuesta.jpg | Bin 0 -> 23423 bytes .../public/rust-laspalmas-dark.png | Bin 0 -> 20597 bytes .../public/rust-laspalmas-dark.svg | 68 + .../rust-laspalmas-wallpaper-4k-dak.png | Bin 0 -> 530081 bytes .../rust-laspalmas-wallpaper-4k-light.png | Bin 0 -> 546925 bytes assets/presentation/public/rust-laspalmas.svg | 1 + .../tim-johnson-430Ad4CRkhk-unsplash.jpg | Bin 0 -> 1146536 bytes .../public/verdad_e_integridad_Gandhi.jpg | Bin 0 -> 582439 bytes .../vitaly-gariev-a4ignFkqk4Y-unsplash.jpg | Bin 0 -> 232897 bytes assets/presentation/public/w-arch-diag-v2.svg | 703 + .../public/wordcloud-builtwith.svg | 462 + .../public/wordcloud-rust-ecosystem.svg | 762 + .../public/wordcloud-rust-features.svg | 714 + .../presentation/public/wordcloud-skills.svg | 634 + .../reader-script-en-live-30min.md | 898 + assets/presentation/reader-script-en-live.md | 885 + .../reader-script-en-pronunciation.md | 1311 ++ .../presentation/reader-script-en-simple.md | 957 + assets/presentation/reader-script-en.md | 638 + assets/presentation/run.sh | 3 + assets/presentation/rustikon.md | 1288 ++ assets/presentation/save_slides.md | 1291 ++ assets/presentation/setup/image-overlay.ts | 34 + assets/presentation/setup/shiki.ts | 9 + assets/presentation/slides.md | 1386 ++ assets/presentation/slides/index.html | 402 + assets/presentation/slidev.config.ts | 38 + assets/presentation/style.css | 341 + assets/presentation/talk-structure.md | 628 + assets/presentation/theme/dark-rust.css | 327 + assets/presentation/theme/rust-vibe/README.md | 103 + assets/presentation/theme/rust-vibe/index.ts | 15 + .../theme/rust-vibe/layoutHelper.ts | 38 + .../theme/rust-vibe/layouts/cover.vue | 20 + .../presentation/theme/rust-vibe/package.json | 20 + .../theme/rust-vibe/styles/index.css | 664 + .../theme/rust-vibe/styles/index.css.backup | 380 + assets/save/a.svg | 184 + assets/save/b.svg | 169 + assets/save/c.svg | 211 + assets/web/architecture-diagram.html | 2 +- assets/web/index.html | 2 +- assets/web/ontoref_graph_view-dark.png | Bin 0 -> 362093 bytes assets/web/ontoref_graph_view-light.png | Bin 0 -> 371553 bytes assets/web/src/index.html | 1357 +- crates/ontoref-daemon/Cargo.toml | 6 +- crates/ontoref-daemon/src/actors.rs | 111 +- crates/ontoref-daemon/src/api.rs | 1384 +- crates/ontoref-daemon/src/cache.rs | 68 + crates/ontoref-daemon/src/lib.rs | 2 +- crates/ontoref-daemon/src/main.rs | 609 +- crates/ontoref-daemon/src/mcp/mod.rs | 31 +- crates/ontoref-daemon/src/nats.rs | 180 +- crates/ontoref-daemon/src/notifications.rs | 29 +- crates/ontoref-daemon/src/registry.rs | 576 +- crates/ontoref-daemon/src/search.rs | 195 + crates/ontoref-daemon/src/seed.rs | 76 +- crates/ontoref-daemon/src/session.rs | 132 +- crates/ontoref-daemon/src/sync.rs | 172 + crates/ontoref-daemon/src/ui/auth.rs | 19 +- crates/ontoref-daemon/src/ui/backlog_ncl.rs | 54 +- crates/ontoref-daemon/src/ui/handlers.rs | 411 +- crates/ontoref-daemon/src/ui/login.rs | 60 +- crates/ontoref-daemon/src/ui/mod.rs | 13 +- crates/ontoref-daemon/src/ui/ncl_write.rs | 158 + crates/ontoref-daemon/src/ui/qa_ncl.rs | 56 +- crates/ontoref-daemon/src/watcher.rs | 165 +- .../templates/pages/manage.html | 50 +- .../templates/pages/manage_login.html | 44 + .../templates/pages/project_picker.html | 7 + install/check-config-sync.nu | 93 + install/config-setup.nu | 140 + install/gen-projects.nu | 124 + install/gen-remote-projects.nu | 130 + install/hooks/post-commit | 43 + install/hooks/post-merge | 44 + install/install.nu | 211 + install/ontoref-daemon-boot | 123 + install/ontoref-global | 2 +- install/resources/config.ncl | 144 + install/resources/projects.ncl | 5 + install/resources/remote-projects.ncl | 6 + install/resources/schemas/ontoref-project.ncl | 29 + install/resources/streams.json | 12 + justfiles/ci.just | 53 +- nats/streams.json | 32 + ontology/schemas/bootstrap.ncl | 42 + reflection/backlog.ncl | 79 + reflection/bin/ontoref.nu | 601 +- reflection/forms/config.ncl | 233 + reflection/forms/config.ncl.j2 | 133 + reflection/hooks/git-event.nu | 30 + reflection/modules/opmode.nu | 198 + reflection/modules/store.nu | 57 +- reflection/nulib/bootstrap.nu | 105 + templates/ontology/core.ncl | 5 +- templates/ontoref-config.ncl | 2 +- templates/project.ncl | 18 + templates/remote-project.ncl | 15 + 182 files changed, 48683 insertions(+), 1109 deletions(-) rename {.typedialog => .ontoref/.typedialog}/ci/README.md (99%) rename {.typedialog => .ontoref/.typedialog}/ci/config.ncl (100%) rename {.typedialog => .ontoref/.typedialog}/ci/config.ncl.20260312_005551.bak (100%) rename {.typedialog => .ontoref/.typedialog}/ci/configure.sh (100%) rename {.typedialog => .ontoref/.typedialog}/ci/envrc (100%) rename {.typedialog => .ontoref/.typedialog}/ci/form.toml (100%) delete mode 100644 .ontoref/README.md delete mode 100644 .ontoref/gen-registry.nu delete mode 100644 .ontoref/logs create mode 100644 .ontoref/project.ncl delete mode 100644 .ontoref/registry.ncl delete mode 100644 .ontoref/registry.toml create mode 100644 adrs/adr-004-ncl-pipe-bootstrap-pattern.ncl create mode 100644 adrs/adr-005-unified-auth-session-model.ncl create mode 100644 assets/logo_prompt.md create mode 100644 assets/presentation/.gitignore create mode 100644 assets/presentation/.tmp-check-slidev-count.mjs create mode 100644 assets/presentation/.tmp-critical-blocks.mjs create mode 100644 assets/presentation/.tmp-regenerate-reader-docs.mjs create mode 100644 assets/presentation/00_talk-structure.md create mode 100644 assets/presentation/01_talk-structure.md create mode 100644 assets/presentation/2026-02-17-notas_voz.md create mode 100644 assets/presentation/2026-02-27-notas_voz_rust.md create mode 100644 assets/presentation/QUICKSTART.md create mode 100644 assets/presentation/README.md create mode 100644 assets/presentation/TO_CHANGE.md create mode 100644 assets/presentation/_slides.md create mode 100644 assets/presentation/abstract_en.md create mode 100644 assets/presentation/ajusta_texto.md create mode 100644 assets/presentation/components/Footer.vue create mode 100644 assets/presentation/components/README.md create mode 100644 assets/presentation/components/YearsCounter.vue create mode 100644 assets/presentation/critical-phrases-blocks-30min.md create mode 100644 assets/presentation/fix_slides.md create mode 100644 assets/presentation/images/arch-diag-v2.svg create mode 100644 assets/presentation/images/blackieshoot-fuR0Iwu5dkk-unsplash.jpg create mode 100644 assets/presentation/images/charles-assuncao-1BbOtIqx21I-unsplash.jpg create mode 100644 assets/presentation/images/cleo-heck-1-l3ds6xcVI-unsplash.jpg create mode 100644 assets/presentation/images/jon-tyson-a4Cm6EnYtls-unsplash.jpg create mode 100644 assets/presentation/images/kenny-Gx1raEg_3Zw-unsplash.jpg create mode 100644 assets/presentation/images/sincerely-media-CWL6tTDN31w-unsplash.jpg create mode 100644 assets/presentation/images/w-arch-diag-v2.svg create mode 100644 assets/presentation/key-moments-storytelling.md create mode 100644 assets/presentation/last_slides.md create mode 100644 assets/presentation/org_slides.md create mode 100644 assets/presentation/package.json create mode 100644 assets/presentation/public/3XXSKa4jKaM.webp create mode 100644 assets/presentation/public/Rust-mascot.png create mode 100644 assets/presentation/public/andrew-neel-jtsW--Z6bFw-unsplash.jpg create mode 100644 assets/presentation/public/arch-diag-v2.svg create mode 100644 assets/presentation/public/clouds_lpa.jpg create mode 100644 assets/presentation/public/crate_in_wall.jpg create mode 100644 assets/presentation/public/enyesque-rust-laspalmas.svg create mode 100644 assets/presentation/public/failed.png create mode 100644 assets/presentation/public/ferris-celebration.svg create mode 100644 assets/presentation/public/ferris-heart.svg create mode 100644 assets/presentation/public/ferris.svg create mode 100644 assets/presentation/public/fethi-bouhaouchine-sTZ_pR8Nns0-unsplash.jpg create mode 100644 assets/presentation/public/humildad_y_sabiduria_socrates.jpg create mode 100644 assets/presentation/public/inovacion_y_cambio_einstein.jpg create mode 100644 assets/presentation/public/jesusperez.png create mode 100644 assets/presentation/public/jesusperez_w.png create mode 100644 assets/presentation/public/jesusperez_w.svg create mode 100644 assets/presentation/public/jpl-image.svg create mode 100644 assets/presentation/public/jpl-imago-160.png create mode 100644 assets/presentation/public/jpl-imago-320.png create mode 100644 assets/presentation/public/jpl-imago-static.svg create mode 100644 assets/presentation/public/jpl-imago-wallpaper-4k.png create mode 100644 assets/presentation/public/jr-korpa-vg0Mph2RmI4-unsplash.jpg create mode 100644 assets/presentation/public/jude-infantini-mI-QcAP95Ok-unsplash.jpg create mode 100644 assets/presentation/public/mYBMP8pW4uQ.webp create mode 100644 assets/presentation/public/ontoref-logo.svg create mode 100644 assets/presentation/public/ontoref-text.svg create mode 100644 assets/presentation/public/ontoref_img.svg create mode 100644 assets/presentation/public/ontoref_text.svg create mode 100644 assets/presentation/public/provisioning.svg create mode 100644 assets/presentation/public/qr-code-encuesta.jpg create mode 100644 assets/presentation/public/rust-laspalmas-dark.png create mode 100644 assets/presentation/public/rust-laspalmas-dark.svg create mode 100644 assets/presentation/public/rust-laspalmas-wallpaper-4k-dak.png create mode 100644 assets/presentation/public/rust-laspalmas-wallpaper-4k-light.png create mode 100644 assets/presentation/public/rust-laspalmas.svg create mode 100644 assets/presentation/public/tim-johnson-430Ad4CRkhk-unsplash.jpg create mode 100644 assets/presentation/public/verdad_e_integridad_Gandhi.jpg create mode 100644 assets/presentation/public/vitaly-gariev-a4ignFkqk4Y-unsplash.jpg create mode 100644 assets/presentation/public/w-arch-diag-v2.svg create mode 100644 assets/presentation/public/wordcloud-builtwith.svg create mode 100644 assets/presentation/public/wordcloud-rust-ecosystem.svg create mode 100644 assets/presentation/public/wordcloud-rust-features.svg create mode 100644 assets/presentation/public/wordcloud-skills.svg create mode 100644 assets/presentation/reader-script-en-live-30min.md create mode 100644 assets/presentation/reader-script-en-live.md create mode 100644 assets/presentation/reader-script-en-pronunciation.md create mode 100644 assets/presentation/reader-script-en-simple.md create mode 100644 assets/presentation/reader-script-en.md create mode 100755 assets/presentation/run.sh create mode 100644 assets/presentation/rustikon.md create mode 100644 assets/presentation/save_slides.md create mode 100644 assets/presentation/setup/image-overlay.ts create mode 100644 assets/presentation/setup/shiki.ts create mode 100644 assets/presentation/slides.md create mode 100644 assets/presentation/slides/index.html create mode 100644 assets/presentation/slidev.config.ts create mode 100644 assets/presentation/style.css create mode 100644 assets/presentation/talk-structure.md create mode 100644 assets/presentation/theme/dark-rust.css create mode 100644 assets/presentation/theme/rust-vibe/README.md create mode 100644 assets/presentation/theme/rust-vibe/index.ts create mode 100644 assets/presentation/theme/rust-vibe/layoutHelper.ts create mode 100644 assets/presentation/theme/rust-vibe/layouts/cover.vue create mode 100644 assets/presentation/theme/rust-vibe/package.json create mode 100644 assets/presentation/theme/rust-vibe/styles/index.css create mode 100644 assets/presentation/theme/rust-vibe/styles/index.css.backup create mode 100644 assets/save/a.svg create mode 100644 assets/save/b.svg create mode 100644 assets/save/c.svg create mode 100644 assets/web/ontoref_graph_view-dark.png create mode 100644 assets/web/ontoref_graph_view-light.png create mode 100644 crates/ontoref-daemon/src/sync.rs create mode 100644 crates/ontoref-daemon/src/ui/ncl_write.rs create mode 100644 crates/ontoref-daemon/templates/pages/manage_login.html create mode 100644 install/check-config-sync.nu create mode 100644 install/config-setup.nu create mode 100644 install/gen-projects.nu create mode 100644 install/gen-remote-projects.nu create mode 100644 install/hooks/post-commit create mode 100644 install/hooks/post-merge create mode 100755 install/install.nu create mode 100755 install/ontoref-daemon-boot create mode 100644 install/resources/config.ncl create mode 100644 install/resources/projects.ncl create mode 100644 install/resources/remote-projects.ncl create mode 100644 install/resources/schemas/ontoref-project.ncl create mode 100644 install/resources/streams.json create mode 100644 nats/streams.json create mode 100644 ontology/schemas/bootstrap.ncl create mode 100644 reflection/backlog.ncl create mode 100644 reflection/forms/config.ncl create mode 100644 reflection/forms/config.ncl.j2 create mode 100644 reflection/hooks/git-event.nu create mode 100644 reflection/modules/opmode.nu create mode 100644 reflection/nulib/bootstrap.nu create mode 100644 templates/project.ncl create mode 100644 templates/remote-project.ncl diff --git a/.ontology/core.ncl b/.ontology/core.ncl index 6679493..8a43841 100644 --- a/.ontology/core.ncl +++ b/.ontology/core.ncl @@ -75,6 +75,9 @@ let d = import "../ontology/defaults/core.ncl" in "adrs/_template.ncl", "adrs/adr-001-protocol-as-standalone-project.ncl", "adrs/adr-002-daemon-for-caching-and-notification-barrier.ncl", + "adrs/adr-003-qa-and-knowledge-persistence-as-ncl.ncl", + "adrs/adr-004-ncl-pipe-bootstrap-pattern.ncl", + "adrs/adr-005-unified-auth-session-model.ncl", "CHANGELOG.md", ], }, @@ -157,18 +160,77 @@ let d = import "../ontology/defaults/core.ncl" in name = "Ontoref Daemon", pole = 'Yang, level = 'Practice, - description = "Runtime support daemon for the ontoref protocol. Provides NCL export caching, file watching, actor registry, notification barrier, HTTP API, MCP server (stdio + streamable-HTTP), Q&A NCL persistence, quick-actions catalog, and passive drift observation. Reads .ontoref/config.ncl at startup.", + description = "Runtime support daemon for the ontoref protocol. Provides NCL export caching, file watching, actor registry, notification barrier, HTTP API, MCP server (stdio + streamable-HTTP), Q&A NCL persistence, quick-actions catalog, passive drift observation, and unified auth/session management (key exchange, Bearer tokens, per-project and daemon-level admin, session list/revoke). Launched via ADR-004 NCL pipe bootstrap: nickel export config.ncl | ontoref-daemon.bin --config-stdin. Binary installed as ontoref-daemon.bin; bootstrapper as ontoref-daemon.", invariant = false, artifact_paths = [ "crates/ontoref-daemon/", + "install/ontoref-daemon-boot", + "install/install.nu", + "nats/streams.json", "reflection/modules/services.nu", "crates/ontoref-daemon/src/ui/qa_ncl.rs", "crates/ontoref-daemon/src/ui/drift_watcher.rs", "crates/ontoref-daemon/src/mcp/mod.rs", + "crates/ontoref-daemon/src/session.rs", + "crates/ontoref-daemon/src/ui/auth.rs", + "crates/ontoref-daemon/src/ui/login.rs", "justfiles/ci.just", ], }, + d.make_node { + id = "unified-auth-model", + name = "Unified Auth Model", + pole = 'Yang, + level = 'Practice, + description = "All surfaces (CLI, UI, MCP) exchange a raw key for a UUID v4 session token via POST /sessions (30-day lifetime, O(1) lookup vs O(~100ms) argon2 per request). Project keys carry role (admin|viewer) and label for audit trail. Daemon admin sessions use virtual slug '_daemon'. ONTOREF_TOKEN injected as Bearer by CLI automatically. Sessions have a stable public id (distinct from bearer token) for safe list/revoke operations. Key rotation revokes all sessions for the rotated project.", + invariant = false, + artifact_paths = [ + "crates/ontoref-daemon/src/session.rs", + "crates/ontoref-daemon/src/api.rs", + "crates/ontoref-daemon/src/registry.rs", + "crates/ontoref-daemon/src/ui/login.rs", + "crates/ontoref-daemon/src/ui/auth.rs", + "reflection/modules/store.nu", + "install/resources/schemas/ontoref-project.ncl", + ], + }, + + d.make_node { + id = "project-onboarding", + name = "Project Onboarding", + pole = 'Yang, + level = 'Practice, + description = "Idempotent onboarding via `ontoref setup`. Creates .ontoref/project.ncl, .ontoref/config.ncl (with logo auto-detection in assets/), .ontology/ scaffold, adrs/, reflection/modes/, backlog.ncl, qa.ncl, git hooks, and registers in projects.ncl. Supports --kind (repo_kind) and --parent (framework layers + browse modes for implementation children). Bootstrap key generation via --gen-keys ['admin:label' 'viewer:label']: idempotent (no-op if keys exist), hashes via daemon binary, prints passwords once.", + invariant = false, + artifact_paths = [ + "reflection/bin/ontoref.nu", + "templates/project.ncl", + "templates/ontoref-config.ncl", + "templates/ontology/", + "install/gen-projects.nu", + "install/resources/schemas/ontoref-project.ncl", + ], + }, + + d.make_node { + id = "daemon-config-management", + name = "Daemon Config Management", + pole = 'Yang, + level = 'Practice, + description = "Install and configuration infrastructure for ontoref-daemon. Global config at ~/.config/ontoref/config.ncl (Nickel, type-checked). Browser-based editing via typedialog roundtrip: form (config.ncl) → browser → Tera template (config.ncl.j2) → updated config.ncl. CI guard (check-config-sync.nu) enforces form/template parity on every commit. Global NATS topology at ~/.config/ontoref/streams.json; project-local override via nats/streams.json and NATS_STREAMS_CONFIG env var. Config validation + liveness probes via config-setup.nu.", + invariant = false, + artifact_paths = [ + "install/resources/config.ncl", + "install/resources/streams.json", + "install/config-setup.nu", + "install/check-config-sync.nu", + "reflection/forms/config.ncl", + "reflection/forms/config.ncl.j2", + "reflection/nulib/bootstrap.nu", + ], + }, + d.make_node { id = "qa-knowledge-store", name = "Q&A Knowledge Store", @@ -239,6 +301,9 @@ let d = import "../ontology/defaults/core.ncl" in { from = "qa-knowledge-store", to = "coder-process-memory", kind = 'Complements, weight = 'High, note = "Q&A is the persistent layer; coder.nu is the session capture layer. Together they form the full memory stack." }, { from = "ontoref-daemon", to = "qa-knowledge-store", kind = 'Contains, weight = 'High }, + { from = "daemon-config-management", to = "ontoref-daemon", kind = 'DependsOn, weight = 'High }, + { from = "daemon-config-management", to = "adopt-ontoref-tooling", kind = 'Complements, weight = 'Medium, + note = "Config management is part of the adoption surface — new projects get config.ncl and streams.json during install." }, # Quick Actions edges { from = "quick-actions", to = "reflection-modes", kind = 'DependsOn, weight = 'High, @@ -254,5 +319,18 @@ let d = import "../ontology/defaults/core.ncl" in { from = "drift-observation", to = "reflection-modes", kind = 'DependsOn, weight = 'High, note = "Invokes sync-ontology mode steps (scan, diff) as read-only sub-processes." }, + # Unified Auth Model edges + { from = "unified-auth-model", to = "ontoref-daemon", kind = 'ManifestsIn, weight = 'High }, + { from = "unified-auth-model", to = "no-enforcement", kind = 'Contradicts, weight = 'Low, + note = "Auth is opt-in per project (no keys = open deployment). When keys are configured enforcement is real, but the protocol itself never mandates it." }, + { from = "ontoref-daemon", to = "unified-auth-model", kind = 'Contains, weight = 'High }, + + # Project Onboarding edges + { from = "project-onboarding", to = "adopt-ontoref-tooling", kind = 'Complements, weight = 'High, + note = "adopt-ontoref-tooling is the migration surface for existing projects; project-onboarding is the first-class setup for new projects." }, + { from = "project-onboarding", to = "unified-auth-model", kind = 'DependsOn, weight = 'Medium, + note = "--gen-keys bootstraps the first keys into project.ncl during setup." }, + { from = "project-onboarding", to = "daemon-config-management", kind = 'DependsOn, weight = 'Medium }, + ], } diff --git a/.ontology/manifest.ncl b/.ontology/manifest.ncl index 741262a..f117406 100644 --- a/.ontology/manifest.ncl +++ b/.ontology/manifest.ncl @@ -28,9 +28,9 @@ m.make_manifest { }, m.make_layer { id = "tooling", - paths = ["reflection/", "ontoref"], + paths = ["reflection/", "install/", "nats/", "templates/", "ontoref"], committed = true, - description = "Operational tooling: Nushell modules, modes, forms, dispatcher, and bash entry point.", + description = "Operational tooling: Nushell modules, modes, forms, dispatcher, bash entry point, install scripts, default config resources, and NATS stream topology.", }, m.make_layer { id = "crates", diff --git a/.ontology/state.ncl b/.ontology/state.ncl index de4c20f..4200cce 100644 --- a/.ontology/state.ncl +++ b/.ontology/state.ncl @@ -25,7 +25,7 @@ let d = import "../ontology/defaults/state.ncl" in to = "protocol-stable", condition = "ADR-001 accepted, ontoref.dev published, at least two external projects consuming the protocol.", catalyst = "First external adoption.", - blocker = "ontoref.dev not yet published; no external consumers yet. Entry point UX fixed (no-args usage message, bash set -u safety).", + blocker = "ontoref.dev not yet published; no external consumers yet. Auth model complete (session exchange, CLI Bearer, key rotation invalidation). Install pipeline: config form roundtrip and NATS topology operational; check-config-sync CI guard present.", horizon = 'Months, }, ], @@ -35,7 +35,7 @@ let d = import "../ontology/defaults/state.ncl" in id = "self-description-coverage", name = "Self-Description Coverage", description = "How completely ontoref describes itself using its own protocol.", - current_state = "modes-and-web-present", + current_state = "fully-self-described", desired_state = "fully-self-described", horizon = 'Weeks, states = [], @@ -52,8 +52,8 @@ let d = import "../ontology/defaults/state.ncl" in from = "modes-and-web-present", to = "fully-self-described", condition = "At least 3 ADRs accepted, reflection/backlog.ncl present, describe project returns complete picture.", - catalyst = "ADR-001 and ADR-002 authored using ontoref against itself in session 2026-03-12.", - blocker = "1 more ADR needed to reach 3. reflection/backlog.ncl not present.", + catalyst = "ADR-001–ADR-004 authored (4 ADRs present, 3+ threshold met). Auth model, project onboarding, and session management nodes added to core.ncl in session 2026-03-13.", + blocker = "none", horizon = 'Weeks, }, ], @@ -88,5 +88,46 @@ let d = import "../ontology/defaults/state.ncl" in ], }, + d.make_dimension { + id = "operational-mode", + name = "Operational Mode", + description = "Runtime connectivity mode: local (files only) or daemon (push-based DB projection). Auto-detected on each command; transitions trigger hook updates and sync. Daemon launched via ADR-004 NCL pipe bootstrap (ontoref-daemon-boot); NATS topology resolved from NATS_STREAMS_CONFIG env var (global ~/.config/ontoref/streams.json) or project-local nats/streams.json.", + current_state = "local", + desired_state = "daemon", + horizon = 'Continuous, + states = [ + d.make_state { + id = "local", + name = "Local", + description = "No daemon. All operations read from files. Hooks are no-ops. Safe for offline or repo-only work.", + tension = 'Low, + }, + d.make_state { + id = "daemon", + name = "Daemon", + description = "Daemon reachable. Ontology projected into DB on each sync. Hooks push on git merge/checkout. NATS events available.", + tension = 'Low, + }, + ], + transitions = [ + d.make_transition { + from = "local", + to = "daemon", + condition = "Daemon reachable at ONTOREF_DAEMON_URL and DB available (if db feature enabled).", + catalyst = "Daemon started, network restored, or first onboarding after install.", + blocker = "Daemon not running or DB not configured.", + horizon = 'Continuous, + }, + d.make_transition { + from = "daemon", + to = "local", + condition = "Daemon unreachable or DB unavailable.", + catalyst = "Network loss, daemon stopped, or offline work.", + blocker = "none", + horizon = 'Continuous, + }, + ], + }, + ], } diff --git a/.typedialog/ci/README.md b/.ontoref/.typedialog/ci/README.md similarity index 99% rename from .typedialog/ci/README.md rename to .ontoref/.typedialog/ci/README.md index 203358a..d2b5de2 100644 --- a/.typedialog/ci/README.md +++ b/.ontoref/.typedialog/ci/README.md @@ -89,7 +89,7 @@ vim .typedialog/ci/config.ncl **This project uses Nickel format by default** for all configuration files. -### Why Nickel? +### Why Nickel - ✅ **Typed configuration** - Static type checking with `nickel typecheck` - ✅ **Documentation** - Generate docs with `nickel doc config.ncl` @@ -313,7 +313,7 @@ Edit `config.ncl` and add under `ci.tools`: enable_pre_commit = false ``` -## Need Help? +## Need Help For detailed documentation, see: - $env.TOOLS_PATH/dev-system/ci/docs/configuration-guide.md diff --git a/.typedialog/ci/config.ncl b/.ontoref/.typedialog/ci/config.ncl similarity index 100% rename from .typedialog/ci/config.ncl rename to .ontoref/.typedialog/ci/config.ncl diff --git a/.typedialog/ci/config.ncl.20260312_005551.bak b/.ontoref/.typedialog/ci/config.ncl.20260312_005551.bak similarity index 100% rename from .typedialog/ci/config.ncl.20260312_005551.bak rename to .ontoref/.typedialog/ci/config.ncl.20260312_005551.bak diff --git a/.typedialog/ci/configure.sh b/.ontoref/.typedialog/ci/configure.sh similarity index 100% rename from .typedialog/ci/configure.sh rename to .ontoref/.typedialog/ci/configure.sh diff --git a/.typedialog/ci/envrc b/.ontoref/.typedialog/ci/envrc similarity index 100% rename from .typedialog/ci/envrc rename to .ontoref/.typedialog/ci/envrc diff --git a/.typedialog/ci/form.toml b/.ontoref/.typedialog/ci/form.toml similarity index 100% rename from .typedialog/ci/form.toml rename to .ontoref/.typedialog/ci/form.toml diff --git a/.ontoref/README.md b/.ontoref/README.md deleted file mode 100644 index c9264a2..0000000 --- a/.ontoref/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# .ontoref/ - -Runtime configuration for the `ontoref-daemon`. - -## Registry - -`registry.toml` is **derived** — do not edit it by hand. - -The source of truth is `registry.ncl`, which carries a typed Nickel schema: - -| Field | Type | Notes | -|-------|------|-------| -| `slug` | `String` | Unique project identifier | -| `root` | `String` | Absolute path to the project root | -| `keys` | `Array KeyEntry` | Optional. Each entry: `role` (`admin`\|`viewer`) + `hash` (Argon2id PHC) | - -### Why not validate paths in Nickel - -Nickel is a pure configuration language — no filesystem access. -Path existence is validated by the generator script at write time, not by the schema. -The schema enforces structure and types; the script enforces runtime semantics. - -### Generating registry.toml - -```sh -nu .ontoref/gen-registry.nu -# or via just: -just gen-registry -``` - -Projects whose `root` path does not exist are warned and skipped. -This is intentional: the registry may reference projects present on some machines but not others. - -### Adding a key (HTTP auth) - -```sh -# 1. Generate the hash -ontoref-daemon --hash-password - -# 2. Add the entry to registry.ncl under the target project's keys array -# 3. Regenerate -just gen-registry -``` diff --git a/.ontoref/config.ncl b/.ontoref/config.ncl index ec623d0..020b6cd 100644 --- a/.ontoref/config.ncl +++ b/.ontoref/config.ncl @@ -11,10 +11,10 @@ log = { level = "info", - path = ".ontoref/logs", + path = "logs", rotation = "daily", compress = false, - archive = ".ontoref/logs-archive", + archive = "logs-archive", max_files = 7, }, diff --git a/.ontoref/gen-registry.nu b/.ontoref/gen-registry.nu deleted file mode 100644 index abca6a3..0000000 --- a/.ontoref/gen-registry.nu +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env nu -# .ontoref/gen-registry.nu -# Reads registry.ncl, validates project paths, and writes registry.toml. -# registry.ncl is the source of truth; registry.toml is derived — do not edit it by hand. - -def main [] { - let script_dir = ($env.CURRENT_FILE | path dirname) - let ncl_file = $"($script_dir)/registry.ncl" - let toml_file = $"($script_dir)/registry.toml" - - let result = (do { ^nickel export --format json $ncl_file } | complete) - if $result.exit_code != 0 { - error make { msg: $"nickel export failed:\n($result.stderr)" } - } - - let registry = ($result.stdout | from json) - - let valid_projects = ($registry.projects | each { |p| - if ($p.root | path exists) { - $p - } else { - print $" (ansi yellow)WARN(ansi reset) project '($p.slug)': root not found at ($p.root) — skipping" - null - } - } | compact) - - let skipped = ($registry.projects | length) - ($valid_projects | length) - if $skipped > 0 { - print $" (ansi yellow)($skipped) project(s) skipped due to missing paths(ansi reset)" - } - - { projects: $valid_projects } | to toml | save -f $toml_file - - print $" (ansi green)OK(ansi reset) ($valid_projects | length) project\(s\) written to ($toml_file)" -} diff --git a/.ontoref/logs b/.ontoref/logs deleted file mode 100644 index 21fc202..0000000 --- a/.ontoref/logs +++ /dev/null @@ -1,41 +0,0 @@ -{"ts":"2026-03-12T02:15:07+0000","author":"unknown","actor":"developer","level":"read","action":"adr list"} -{"ts":"2026-03-12T02:58:09+0000","author":"unknown","actor":"developer","level":"read","action":"adr list"} -{"ts":"2026-03-12T03:04:52+0000","author":"unknown","actor":"developer","level":"read","action":"adr list"} -{"ts":"2026-03-12T03:05:21+0000","author":"unknown","actor":"developer","level":"read","action":"adr list"} -{"ts":"2026-03-12T03:05:58+0000","author":"unknown","actor":"developer","level":"read","action":"adr list"} -{"ts":"2026-03-12T03:06:03+0000","author":"unknown","actor":"developer","level":"read","action":"adr list"} -{"ts":"2026-03-12T03:11:03+0000","author":"unknown","actor":"agent","level":"read","action":"adr list"} -{"ts":"2026-03-12T03:12:05+0000","author":"unknown","actor":"developer","level":"read","action":"adr list"} -{"ts":"2026-03-12T03:12:19+0000","author":"unknown","actor":"developer","level":"read","action":"adr list"} -{"ts":"2026-03-12T03:47:17+0000","author":"unknown","actor":"developer","level":"read","action":"adr list"} -{"ts":"2026-03-12T06:41:52+0000","author":"unknown","actor":"developer","level":"read","action":"adr list"} -{"ts":"2026-03-12T06:41:59+0000","author":"unknown","actor":"developer","level":"read","action":"adr list"} -{"ts":"2026-03-12T08:50:18+0000","author":"unknown","actor":"developer","level":"read","action":"constraint"} -{"ts":"2026-03-12T08:51:26+0000","author":"unknown","actor":"developer","level":"read","action":"overview"} -{"ts":"2026-03-12T08:52:04+0000","author":"unknown","actor":"developer","level":"read","action":"about"} -{"ts":"2026-03-12T08:52:23+0000","author":"unknown","actor":"developer","level":"read","action":"about"} -{"ts":"2026-03-12T08:52:44+0000","author":"unknown","actor":"developer","level":"read","action":"status"} -{"ts":"2026-03-12T10:20:47+0000","author":"unknown","actor":"developer","level":"read","action":"describe project"} -{"ts":"2026-03-12T18:29:08+0000","author":"unknown","actor":"developer","level":"read","action":"sync scan"} -{"ts":"2026-03-12T18:33:17+0000","author":"unknown","actor":"developer","level":"read","action":"sync diff"} -{"ts":"2026-03-12T18:33:32+0000","author":"unknown","actor":"developer","level":"read","action":"sync scan"} -{"ts":"2026-03-12T18:33:36+0000","author":"unknown","actor":"developer","level":"read","action":"sync diff"} -{"ts":"2026-03-12T18:39:22+0000","author":"unknown","actor":"developer","level":"read","action":"sync scan"} -{"ts":"2026-03-12T18:39:26+0000","author":"unknown","actor":"developer","level":"read","action":"sync diff"} -{"ts":"2026-03-12T18:39:41+0000","author":"unknown","actor":"developer","level":"read","action":"sync scan"} -{"ts":"2026-03-12T18:39:45+0000","author":"unknown","actor":"developer","level":"read","action":"sync diff"} -{"ts":"2026-03-12T18:40:33+0000","author":"unknown","actor":"developer","level":"read","action":"sync scan"} -{"ts":"2026-03-12T18:40:37+0000","author":"unknown","actor":"developer","level":"read","action":"sync diff"} -{"ts":"2026-03-12T18:41:04+0000","author":"unknown","actor":"developer","level":"read","action":"sync scan"} -{"ts":"2026-03-12T18:41:07+0000","author":"unknown","actor":"developer","level":"read","action":"sync diff"} -{"ts":"2026-03-12T18:43:22+0000","author":"unknown","actor":"developer","level":"read","action":"sync scan"} -{"ts":"2026-03-12T18:43:25+0000","author":"unknown","actor":"developer","level":"read","action":"sync diff"} -{"ts":"2026-03-12T18:43:43+0000","author":"unknown","actor":"developer","level":"read","action":"sync scan"} -{"ts":"2026-03-12T18:43:47+0000","author":"unknown","actor":"developer","level":"read","action":"sync diff"} -{"ts":"2026-03-12T20:46:07+0000","author":"unknown","actor":"developer","level":"read","action":"sync scan"} -{"ts":"2026-03-12T20:46:12+0000","author":"unknown","actor":"developer","level":"read","action":"sync diff"} -{"ts":"2026-03-12T20:47:05+0000","author":"unknown","actor":"developer","level":"read","action":"sync scan"} -{"ts":"2026-03-12T20:47:09+0000","author":"unknown","actor":"developer","level":"read","action":"sync diff"} -test -{"ts":"2026-03-13T00:39:33+0000","author":"unknown","actor":"agent","level":"read","action":"adr list"} -{"ts":"2026-03-13T00:40:00+0000","author":"unknown","actor":"developer","level":"read","action":"adr list"} diff --git a/.ontoref/project.ncl b/.ontoref/project.ncl new file mode 100644 index 0000000..95e92f2 --- /dev/null +++ b/.ontoref/project.ncl @@ -0,0 +1,8 @@ +let s = import "ontoref-project.ncl" in + +s.make_project { + slug = "ontoref", + root = "/Users/Akasha/Development/ontoref", + nickel_import_paths = ["/Users/Akasha/Development/ontoref"], + keys = [], +} diff --git a/.ontoref/registry.ncl b/.ontoref/registry.ncl deleted file mode 100644 index 2895c11..0000000 --- a/.ontoref/registry.ncl +++ /dev/null @@ -1,33 +0,0 @@ -# Registry source of truth — consumed by gen-registry.nu to produce registry.toml. -# Path existence is validated at generation time (Nickel is pure; no filesystem access). -# Hash values are Argon2id PHC strings produced by: ontoref-daemon --hash-password - -let KeyRole = [| 'admin, 'viewer |] in - -let KeyEntry = { - role | KeyRole, - hash | String, -} in - -let ProjectEntry = { - slug | String, - root | String, - keys | Array KeyEntry | default = [], -} in - -{ - projects | Array ProjectEntry = [ - { - slug = "ontoref", - root = "/Users/Akasha/Development/ontoref", - }, - { - slug = "typedialog", - root = "/Users/Akasha/Development/typedialog", - }, - { - slug = "stratumiops", - root = "/Users/Akasha/Development/stratumiops", - }, - ], -} diff --git a/.ontoref/registry.toml b/.ontoref/registry.toml deleted file mode 100644 index 0a7c4fa..0000000 --- a/.ontoref/registry.toml +++ /dev/null @@ -1,14 +0,0 @@ -[[projects]] -keys = [] -root = "/Users/Akasha/Development/ontoref" -slug = "ontoref" - -[[projects]] -keys = [] -root = "/Users/Akasha/Development/typedialog" -slug = "typedialog" - -[[projects]] -keys = [] -root = "/Users/Akasha/Development/stratumiops" -slug = "stratumiops" diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d990d3..ccb459c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,169 @@ ADRs referenced below live in `adrs/` as typed Nickel records. ## [Unreleased] +### Auth & Session Model (ADR-005) + +Unified key-to-session token exchange across all surfaces. All work gated on `#[cfg(feature = "ui")]`. + +- `KeyEntry` gains `label: String` (`#[serde(default)]`) — audit trail for key-based sessions. + NCL schema `install/resources/schemas/ontoref-project.ncl` updated accordingly. +- `verify_keys_list` returns `Option` instead of `Option`. +- `SessionEntry` gains `id: String` — stable public identifier distinct from the bearer token, + safe to expose in list responses. Prevents session enumeration by admins. +- `SessionStore` gains secondary `id_index: DashMap` for O(1) `revoke_by_id`. +- New `SessionStore` methods: `list_for_slug`, `list_all`, `revoke_all_for_slug`, + `revoke_by_id(id, acting_slug, acting_role) -> RevokeResult`. +- `POST /sessions` — key → UUID v4 token exchange. Accepts project keys or daemon admin password + (`project: "_daemon"`). Rate-limited. No authentication required (it is the credential exchange endpoint). +- `GET /sessions?project=slug` — list active sessions for a project (viewer+). Without `?project=` + param requires daemon admin and returns all sessions. +- `DELETE /sessions/{id}` — revoke by public id. Project admin: own project only. Daemon admin: any. +- `require_session()` helper: validates UUID v4 Bearer → `SessionEntry`, error boxed (`Box`) + to satisfy `clippy::result_large_err`. +- `check_primary_auth` fast-path: UUID v4 bearer → session lookup (O(1)) before argon2 fallback (~100ms). +- `project_update_keys` (`PUT /projects/{slug}/keys`) now calls `sessions.revoke_all_for_slug` in + addition to actor deregistration. All in-flight UI sessions for the rotated project are immediately + invalidated. +- Daemon admin: `ONTOREF_ADMIN_TOKEN_FILE` (preferred — hash not visible in `ps aux`) or + `ONTOREF_ADMIN_TOKEN`. Sessions use virtual slug `"_daemon"`. +- `manage_login_page` / `manage_login_submit` / `manage_logout` handlers for `/ui/manage/login` + and `/ui/manage/logout`. +- `AdminGuard` redirects to `/ui/manage/login` when `daemon_admin_hash` is set. + +### CLI Bearer Token + +- `bearer-args` exported from `reflection/modules/store.nu`: returns `["-H" "Authorization: Bearer $token"]` + when `ONTOREF_TOKEN` is set, `[]` otherwise. +- `http-get`, `http-post-json`, `http-delete` (new) in `store.nu` use `...$auth` — Bearer injected + transparently, no behavior change when `ONTOREF_TOKEN` is unset. +- `notify-daemon-project-add` and `notify-daemon-project-remove` in `reflection/bin/ontoref.nu` + use `bearer-args`. + +### Project Setup & Onboarding + +- `ontoref setup` is now the primary onboarding command (replaces manual `cp templates/` pattern). +- `--kind ` flag: `Service` (default) | `Library` | `DevWorkspace` | `PublishedCrate` | + `AgentResource` | `Mixed`. +- `--parent ` flag: generates manifest with `implementation` layer + `-framework` layer + and `-browse` op mode for implementation children. +- Logo auto-detection: `setup` scans `assets/` for `-logo.svg`, `.svg`, `logo.svg` + (and `.png` variants); inserts `ui.logo` into generated `config.ncl` when found. +- `--gen-keys ["admin:label" "viewer:label"]` flag: idempotent bootstrap — skips if `role =` already + present in `project.ncl`. Hashes via `ontoref-daemon.bin --hash-password`; prints passwords once + to stdout. +- All `mkdir` calls in setup guarded by `if not (path | path exists)`. + +### Self-Description — on+re Update + +`.ontology/core.ncl` — 3 new Practice nodes, updated `ontoref-daemon` description and `artifact_paths`: + +| Node | Pole | Description | +| --- | --- | --- | +| `unified-auth-model` | Yang | Key→session token exchange; session.id ≠ bearer; revoke on key rotation | +| `project-onboarding` | Yang | `ontoref setup` — idempotent scaffold, `--kind`, `--parent`, `--gen-keys` | + +`ontoref-daemon` node updated: auth/session management added to description and `artifact_paths` +(`session.rs`, `auth.rs`, `login.rs`). + +New edges: `unified-auth-model → ontoref-daemon`, `unified-auth-model → no-enforcement` +(Contradicts/Low — auth is opt-in), `ontoref-daemon → unified-auth-model` (Contains), +`project-onboarding → unified-auth-model` (DependsOn), +`project-onboarding → adopt-ontoref-tooling` (Complements), +`project-onboarding → daemon-config-management` (DependsOn). + +`.ontology/state.ncl` — `self-description-coverage` transitions to `current_state = "fully-self-described"`. +Blocker resolved: `reflection/backlog.ncl` created, ADR-005 recorded. + +`reflection/backlog.ncl` — created with 6 items: bl-001 (ontoref.dev), bl-002 (first external project), +bl-003 (`ontoref keys` CLI), bl-004 (session UI views), bl-005 (Syntaxis migration), +bl-006 (ADR-001 acceptance). + +Previous state: 4 axioms, 2 tensions, 13 practices. Current: 4 axioms, 2 tensions, 15 practices. + +### Protocol + +- ADR-004 accepted: NCL pipe bootstrap pattern — + `nickel export config.ncl | ontoref-daemon.bin --config-stdin`. + Stages: Nickel evaluation → optional SOPS/Vault merge → binary via stdin. + ([adr-004](adrs/adr-004-ncl-pipe-bootstrap-pattern.ncl)) +- ADR-005 accepted: unified key-to-session auth model across CLI, UI, and MCP. Opaque UUID v4 tokens, + `session.id ≠ bearer`, revocation on key rotation, daemon admin via `_daemon` slug. + ([adr-005](adrs/adr-005-unified-auth-session-model.ncl)) + +--- + +### Install Infrastructure + +- `install/` directory reorganized: binaries, bootstrapper, global CLI, resources, and validation + scripts co-located. +- Binary installed as `ontoref-daemon.bin`; public entrypoint bootstrapper installed as `ontoref-daemon`. + Users never call `.bin` directly. +- `install/ontoref-daemon-boot` (renamed from `ontoref-daemon-start`) — NCL pipe bootstrapper + implementing ADR-004. Stages: `nickel export config.ncl` → optional SOPS/Vault secret merge → + `ontoref-daemon.bin --config-stdin`. Supports `--dry-run`, `--sops`, `--vault`. +- `install/ontoref-daemon-boot` sets `NICKEL_IMPORT_PATH` (config dir + platform data dir schemas) + and `NATS_STREAMS_CONFIG` (default `~/.config/ontoref/streams.json`) before launching the binary. +- `install/install.nu` — installs binary, bootstrapper, global `ontoref` CLI (ONTOREF_ROOT baked in + at install time), UI assets, config skeleton, and global NATS topology. + Hash-checked — unchanged files are not overwritten. +- `install/config-setup.nu` — standalone validation script: `nickel typecheck` + `nickel export`, + path existence checks, DB and NATS liveness probes (`nc -z -w2`). +- `install/check-config-sync.nu` — CI guard asserting that every `nickel_path`-bearing field in + `reflection/forms/config.ncl` has a matching `{{ name }}` reference in `reflection/forms/config.ncl.j2`, + and vice versa. Wired into `just ci-lint` and `just ci-full`. + +### Config Management + +- `install/resources/config.ncl` — default global config skeleton with full Nickel contracts + (`Port`, `LogLevel`, `Rotation`, `Actor`, `Severity`). Covers: daemon, db, nats_events, log, + cache, ui, mode_run, actor_init, quick_actions, nickel_import_paths. +- `install/resources/streams.json` — global default NATS JetStream topology: ECOSYSTEM stream + (`ecosystem.>`, 30-day retention), no project-specific consumers. + Installed to `~/.config/ontoref/streams.json`. +- `nats/streams.json` — ontoref project-local topology with `daemon-ontoref` and `cli-notifications` + consumers on ECOSYSTEM stream. +- `reflection/forms/config.ncl` + `reflection/forms/config.ncl.j2` — typedialog roundtrip for + browser-based config editing (`ontoref config-edit`). Form populates fields from existing config + via `nickel_path`; Tera template reconstructs full NCL with all contracts on save. +- `reflection/nulib/bootstrap.nu` — Nu bootstrapper helper updated: `nats-streams-config` function + resolves `NATS_STREAMS_CONFIG` default; env var passed to daemon process via `with-env`. +- Daemon `nats.rs`: empty `streams_config` string → `None`, activating `TopologyConfig::load` fallback + to `NATS_STREAMS_CONFIG` env var. Projects with a local `nats/streams.json` set `streams_config` + explicitly in their config. + +### Daemon Fixes + +- `--config-stdin` now exclusively skips `.ontoref/config.ncl` — project config is never loaded when + stdin config is active. Previously both paths could run. +- DB connection (`stratum-db`) only established when `db.enabled = true` in config. Previously + connected regardless of `enabled` flag. +- NATS connection (`platform-nats`) only established when `nats_events.enabled = true`. + "Connecting to NATS..." log moved after the enabled check. +- `NatsPublisher::connect` signature changed from `config_path: &PathBuf` (re-read file) to + `config: Option<&serde_json::Value>` (already-loaded JSON). Eliminates double file read and ensures + NATS uses the same config source as the rest of the daemon. +- `load_config_overrides` returns `(Option, Option)` — nickel import path + and parsed config JSON returned together. `apply_stdin_config` returns `serde_json::Value` directly. + +### Self-Description — on+re Update + +`.ontology/core.ncl` — 1 new Practice node, updated `ontoref-daemon` description and artifact_paths: + +| Node | Pole | Description | +| --- | --- | --- | +| `daemon-config-management` | Yang | Install + config form/template roundtrip, CI guard, global NATS topology, config-setup validation | + +New edges: `daemon-config-management → ontoref-daemon` (DependsOn), +`daemon-config-management → adopt-ontoref-tooling` (Complements). + +`.ontology/state.ncl` — `operational-mode` dimension description updated to reference ADR-004 bootstrap +and `NATS_STREAMS_CONFIG` mechanism. `protocol-maturity` blocker updated to reflect install pipeline +completion. + +`.ontology/manifest.ncl` — `tooling` layer paths updated to include `install/` and `nats/`. + +Previous state: 4 axioms, 2 tensions, 12 practices. Current: 4 axioms, 2 tensions, 13 practices. + ### Protocol - ADR-001 accepted: ontoref extracted as a standalone protocol project, independent of @@ -24,21 +187,25 @@ ADRs referenced below live in `adrs/` as typed Nickel records. ### Crates -- `ontoref-ontology`: Rust crate for loading `.ontology/*.ncl` as typed structs (`Core`, `Gate`, `State`). Zero stratumiops dependencies. -- `ontoref-reflection`: Rust crate for loading, validating, and executing Reflection modes as NCL DAG contracts. Optional `nats` feature (path dep: `platform-nats`). +- `ontoref-ontology`: Rust crate for loading `.ontology/*.ncl` as typed structs (`Core`, `Gate`, + `State`). Zero stratumiops dependencies. +- `ontoref-reflection`: Rust crate for loading, validating, and executing Reflection modes as NCL + DAG contracts. Optional `nats` feature (path dep: `platform-nats`). - `ontoref-daemon`: Rust crate providing HTTP API (axum), DashMap-backed NCL export cache, notify-based file watcher, and actor registry. Optional `db` feature (path dep: `stratum-db`) and `nats` feature (path dep: `platform-nats`). ### Daemon — Q&A NCL Persistence (`crates/ontoref-daemon/src/ui/qa_ncl.rs`) -Line-level NCL surgery for `reflection/qa.ncl` — same pattern as `backlog_ncl.rs`. No AST parsing, no nickel-lang-core dependency. +Line-level NCL surgery for `reflection/qa.ncl` — same pattern as `backlog_ncl.rs`. No AST parsing, +no nickel-lang-core dependency. - `add_entry` — appends a typed `QaEntry` block before `],` array close; generates sequential `qa-NNN` ids - `update_entry` — in-place field mutation via bidirectional scan (question + answer fields) - `remove_entry` — removes the full block by id using backward scan for `{` and forward scan for `},` HTTP endpoints (all under `#[cfg(feature = "ui")]` except read-only GET): + - `GET /qa-json` — export all Q&A entries as JSON (read-only, always enabled) - `POST /qa/add` — append new entry; returns generated id - `POST /qa/delete` — remove entry by id; invalidates NCL cache @@ -59,7 +226,8 @@ Four new MCP tools exposed to AI agents: | `ontoref_action_list` | List all quick actions from `.ontoref/config.ncl` export. | | `ontoref_action_add` | Create a new reflection mode at `reflection/modes/.ncl` and register it as a quick action. | -Constraint: `ontoref_qa_list` and `ontoref_qa_add` never trigger `apply` steps or modify `.ontology/` files (enforced by ADR-003). +Constraint: `ontoref_qa_list` and `ontoref_qa_add` never trigger `apply` steps or modify `.ontology/` +files (enforced by ADR-003). ### Daemon — Passive Drift Observation (`crates/ontoref-daemon/src/ui/drift_watcher.rs`) @@ -76,11 +244,14 @@ Started from `main.rs` under `#[cfg(feature = "ui")]`; failure to start is non-f ### Tooling -- `reflection/modules/`: 16 Nushell operational modules (`adr.nu`, `backlog.nu`, `coder.nu`, `describe.nu`, `sync.nu`, etc.) -- `reflection/modes/`: 10 NCL DAG operational modes including `adopt_ontoref`, `sync-ontology`, `coder-workflow`, `create-pr` +- `reflection/modules/`: 16 Nushell operational modules (`adr.nu`, `backlog.nu`, `coder.nu`, + `describe.nu`, `sync.nu`, etc.) +- `reflection/modes/`: 10 NCL DAG operational modes including `adopt_ontoref`, `sync-ontology`, + `coder-workflow`, `create-pr` - `reflection/forms/`: 7 interactive NCL forms for ADR lifecycle, backlog, and adoption - `templates/`: Consumer-facing adoption templates (`ontoref-config.ncl`, `ontology/`, `scripts-ontoref`) -- `./ontoref`: Bash entry point with actor auto-detection, advisory file locking, and Nushell version guard (>= 0.110.0) +- `./ontoref`: Bash entry point with actor auto-detection, advisory file locking, and Nushell version + guard (>= 0.110.0) ### Self-Description — on+re Update @@ -100,7 +271,8 @@ New edges: `qa-knowledge-store → dag-formalized`, `qa-knowledge-store → code Previous state: 4 axioms, 2 tensions, 9 practices. Current: 4 axioms, 2 tensions, 12 practices. -`reflection/schemas/qa.ncl` — `QaStore` and `QaEntry` types (id, question, answer, actor, created_at, tags, related, verified). +`reflection/schemas/qa.ncl` — `QaStore` and `QaEntry` types (id, question, answer, actor, created_at, +tags, related, verified). `reflection/qa.ncl` — typed store file, conforms to `QaStore` contract, Nickel typecheck must pass. --- diff --git a/Cargo.lock b/Cargo.lock index f83d56a..87bd092 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2891,8 +2891,10 @@ dependencies = [ "tera", "thiserror 2.0.18", "tokio", + "tokio-stream", "tokio-test", "toml", + "tower", "tower-http", "tracing", "tracing-subscriber", @@ -5238,6 +5240,7 @@ dependencies = [ "futures-core", "pin-project-lite", "tokio", + "tokio-util", ] [[package]] diff --git a/README.md b/README.md index 8b0f3b0..9f90899 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,13 @@ subprocess when unavailable. ### Daemon Capabilities +**Unified Auth Model** — all surfaces (CLI, UI, MCP) exchange a key for a UUID v4 session token +via `POST /sessions`. Token lifetime: 30 days, O(1) lookup. Project keys carry `role` +(admin|viewer) and `label` for audit trail. Daemon-level admin via `ONTOREF_ADMIN_TOKEN_FILE`. +`GET /sessions` and `DELETE /sessions/{id}` for session visibility and revocation. Key rotation +invalidates all sessions for the rotated project. CLI injects `ONTOREF_TOKEN` as Bearer +automatically. + **Q&A Knowledge Store** — accumulated Q&A entries persist to `reflection/qa.ncl` (typed NCL, git-versioned). Not localStorage. Any actor — developer, agent, CI — reads the same store. @@ -67,27 +74,48 @@ After a 15s debounce runs `sync scan + sync diff`; emits an `ontology_drift` not MISSING/STALE/DRIFT/BROKEN items are found. Never applies changes — `apply` is always deliberate. **Quick Actions** — runnable shortcuts over reflection modes, configured as `quick_actions` in -`.ontoref/config.ncl`. Accessible from HTTP (`/actions`), CLI (`./ontoref`), and MCP +`.ontoref/config.ncl`. Accessible from HTTP (`/actions`), CLI (`ontoref`), and MCP (`ontoref_action_list/add`). -## Adopting ontoref - -A consumer project needs exactly two artifacts: +## Install ```sh -# 1. Copy the entry point wrapper -cp templates/scripts-ontoref scripts/ontoref -chmod +x scripts/ontoref - -# 2. Initialize config -mkdir -p .ontoref -cp templates/ontoref-config.ncl .ontoref/config.ncl +just install-daemon # build + install binary, bootstrapper, CLI, UI assets, config skeleton +ontoref config-edit # browser form → ~/.config/ontoref/config.ncl +ontoref-daemon-boot # NCL pipe bootstrap: nickel export config.ncl | daemon --config-stdin +ontoref-daemon-boot --dry-run # preview composed JSON without starting ``` -Or run the adoption mode interactively: +Installed layout (`~/.local/bin/`): + +| Binary | Role | +| --- | --- | +| `ontoref` | Global CLI dispatcher — all reflection modes, ADR lifecycle, daemon control | +| `ontoref-daemon` | Bootstrapper (public entrypoint) — validates config via Nickel, pipes JSON to binary | +| `ontoref-daemon.bin` | Compiled Rust binary — never called directly | + +Global config at `~/.config/ontoref/config.ncl` (type-checked Nickel). Global NATS stream topology at +`~/.config/ontoref/streams.json`. Project-local topology override via `nats/streams.json` + +`nats_events.streams_config` in `.ontoref/config.ncl`. + +## Onboarding a project ```sh -./ontoref --actor developer adopt_ontoref +cd /path/to/my-project +ontoref setup # idempotent; kind: Service by default +ontoref setup --kind Library # Library | DevWorkspace | PublishedCrate | AgentResource | Mixed +ontoref setup --parent /path/to/fw # implementation child: adds framework layer + browse mode +ontoref setup --gen-keys ["admin:dev" "viewer:ci"] # bootstrap auth keys (no-op if keys already exist) +``` + +`ontoref setup` creates `.ontoref/project.ncl`, `.ontoref/config.ncl` (with logo auto-detection), +`.ontology/` scaffold, `adrs/`, `reflection/modes/`, `backlog.ncl`, `qa.ncl`, git hooks, and +registers the project in `~/.config/ontoref/projects.ncl`. + +For existing projects that predate `setup`, the adoption mode is still available: + +```sh +ontoref --actor developer adopt_ontoref ``` `ONTOREF_PROJECT_ROOT` is set by the consumer wrapper — one ontoref checkout serves multiple projects. diff --git a/adrs/adr-004-ncl-pipe-bootstrap-pattern.ncl b/adrs/adr-004-ncl-pipe-bootstrap-pattern.ncl new file mode 100644 index 0000000..50e7a47 --- /dev/null +++ b/adrs/adr-004-ncl-pipe-bootstrap-pattern.ncl @@ -0,0 +1,113 @@ +let d = import "defaults.ncl" in + +d.make_adr { + id = "adr-004", + title = "NCL Pipe Bootstrap — Config Validation and Secret Injection via Unix Pipeline", + status = 'Accepted, + date = "2026-03-13", + + context = "Ontoref-daemon and any process that receives structured config faces two problems: (1) Nickel NCL requires a subprocess to evaluate, introducing a system-call injection surface if the daemon itself calls `nickel export` at runtime; (2) credentials and secrets embedded in config files (TOML, JSON) persist on disk after the process starts, creating a forensic artifact. The existing registry.toml approach (NCL → TOML file → daemon reads file) partially addresses the first problem but not the second — the TOML file remains on disk with hashed credentials. SOPS and Vault are standard secret management tools that produce decrypted output on stdout.", + + decision = "All config delivery to long-running processes follows a three-stage Unix pipeline: Stage 1 — structural validation: `nickel export --format json config.ncl` produces JSON with schema-validated structure but no secret values; Stage 2 — secret injection (optional): SOPS decrypt or Vault lookup merges credentials into the JSON stream; Stage 3 — process bootstrap: the target process reads the composed JSON from stdin via `--config-stdin`. No intermediate file is written to disk. If any stage fails, the pipeline breaks and the process does not start. A bash wrapper script (not Nu — Nu may not be available at service boot time) orchestrates the pipeline. A Nu helper `ncl-bootstrap` provides the same interface for interactive/development use.", + + rationale = [ + { + claim = "Pipe eliminates disk artifacts for secrets", + detail = "Pipe contents are kernel-managed memory, inaccessible to other processes, and ephemeral — no filesystem metadata, no tmpfs entry, nothing survives process termination. A TOML or JSON file on disk persists until explicitly deleted, is accessible to any process running as the same UID, and may appear in filesystem audit logs. For secrets (DB passwords, API keys, Argon2id hashes), the pipe is materially safer.", + }, + { + claim = "Nickel is the validation layer, not the runtime config format", + detail = "The daemon never calls `nickel export` — it only reads JSON from stdin. The NCL schema enforces structural correctness and type safety before the process starts. This separates concerns: NCL for authoring and validation, JSON for delivery. No Nickel subprocess risk at runtime.", + }, + { + claim = "Bash wrapper for service launchers, Nu helper for development", + detail = "System service managers (launchctl, systemd) run in environments where Nu may not be on PATH or may be a different version. The bash wrapper has zero dependencies beyond bash, nickel, and the target binary. The Nu ncl-bootstrap helper provides richer error messages and structured output for interactive development use. Both implement the same pipeline.", + }, + { + claim = "SOPS and Vault integrate as a composable pipeline stage", + detail = "Stage 2 is optional and replaceable. With SOPS: `sops --decrypt secrets.enc.json`. With Vault: `vault kv get -format=json secret/path | jq '.data.data'`. Both produce JSON on stdout. `jq -s '.[0] * .[1]'` merges the structural config with the secrets. The NCL file never contains secret values — only SecretRef placeholders if self-documentation is desired.", + }, + { + claim = "Pipeline failure semantics are correct by default", + detail = "If `nickel export` fails (schema violation, syntax error), stdout is empty or truncated — the target process receives invalid JSON and fails at parse time, before any initialization. If SOPS or Vault fails, same result. The process never starts in a partially-configured state. This is safer than file-based config where the process may start with a stale or invalid file.", + }, + ], + + consequences = { + positive = [ + "No credentials on disk after process startup — ephemeral pipe only", + "NCL schema violations prevent daemon startup — config errors are caught early", + "SOPS and Vault integrate without changes to the daemon — secrets are a pipeline concern", + "Pattern is reusable: any project can adopt ncl-bootstrap for any long-running process", + "Bash wrapper works in Docker, CI, launchctl, systemd without Nu dependency", + ], + negative = [ + "Daemon loses ability to hot-reload config from disk — config changes require restart via wrapper", + "stdin is consumed at startup — daemon must redirect stdin to /dev/null after reading config", + "Pipeline debugging is harder than inspecting a config file — need wrapper --dry-run mode", + "Nu must not be required for the bash wrapper — two implementations of the same pattern to maintain", + ], + }, + + alternatives_considered = [ + { + option = "TOML file on disk (current registry.toml approach)", + why_rejected = "File persists on disk with credentials. Stale file may be read after config changes. Requires explicit cleanup logic. Forensic artifact risk.", + }, + { + option = "Environment variables for secrets", + why_rejected = "Environment variables are visible in /proc/PID/environ on Linux and via `ps eww` on some systems. They persist for the lifetime of the process and are inherited by child processes. Worse attack surface than stdin pipe.", + }, + { + option = "Encrypted TOML file (AES256 at rest)", + why_rejected = "Decryption key must be available at runtime — the problem is deferred, not solved. The decrypted form still passes through disk (tmpfs or swap). Adds a custom encryption layer instead of using standard tools (SOPS, Vault) that the ecosystem already supports.", + }, + { + option = "Daemon reads NCL directly at runtime via nickel-lang-core", + why_rejected = "nickel-lang-core has an unstable Rust API. More critically, it means the daemon can evaluate arbitrary Nickel — including NCL files with system calls via builtins. The pipeline approach ensures the daemon only ever sees validated JSON, never executable Nickel.", + }, + ], + + constraints = [ + { + id = "no-config-file-on-disk", + claim = "The bootstrap pipeline must not write an intermediate config file to disk at any stage", + scope = "scripts/ontoref-daemon-start, reflection/nulib/bootstrap.nu", + severity = 'Hard, + check_hint = "grep -E 'tee|>|tempfile|mktemp' scripts/ontoref-daemon-start", + rationale = "An intermediate file defeats the purpose of the pipeline. If a file is needed for debugging, use --dry-run which prints to stdout only.", + }, + { + id = "bash-wrapper-zero-deps", + claim = "The bash wrapper must depend only on bash, nickel, and the target binary — no Nu, no jq unless SOPS/Vault stage is active", + scope = "scripts/ontoref-daemon-start", + severity = 'Hard, + check_hint = "head -5 scripts/ontoref-daemon-start", + rationale = "System service managers may not have Nu on PATH. The wrapper must be portable across launchctl, systemd, Docker entrypoints.", + }, + { + id = "config-stdin-closes-after-read", + claim = "The target process must redirect stdin to /dev/null after reading the config JSON", + scope = "crates/ontoref-daemon/src/main.rs", + severity = 'Hard, + check_hint = "rg 'config.stdin\\|/dev/null\\|stdin.*close' crates/ontoref-daemon/src/main.rs", + rationale = "stdin left open blocks terminal interaction and causes confusion in interactive sessions. The daemon is a server — it must not hold stdin.", + }, + { + id = "ncl-no-secret-values", + claim = "NCL config files used with ncl-bootstrap must not contain plaintext secret values — only SecretRef placeholders or empty fields", + scope = ".ontoref/config.ncl, APP_SUPPORT/ontoref/config.ncl", + severity = 'Hard, + check_hint = "nickel export .ontoref/config.ncl | jq 'paths(scalars) | select(test(\"password|secret|key|token|hash\"))'", + rationale = "If secrets are in the NCL file, they are readable as plaintext by anyone with filesystem access. Secrets enter the pipeline only at the SOPS/Vault stage.", + }, + ], + + related_adrs = ["adr-002-daemon-for-caching-and-notification-barrier"], + + ontology_check = { + decision_string = "NCL pipe bootstrap — config validation via nickel export piped to process stdin, secrets injected via SOPS/Vault as optional pipeline stage, no intermediate files", + invariants_at_risk = ["protocol-not-runtime"], + verdict = 'Safe, + }, +} diff --git a/adrs/adr-005-unified-auth-session-model.ncl b/adrs/adr-005-unified-auth-session-model.ncl new file mode 100644 index 0000000..acfed31 --- /dev/null +++ b/adrs/adr-005-unified-auth-session-model.ncl @@ -0,0 +1,126 @@ +let d = import "defaults.ncl" in + +d.make_adr { + id = "adr-005", + title = "Unified Key-to-Session Auth Model Across CLI, UI, and MCP", + status = 'Accepted, + date = "2026-03-13", + + context = "ontoref-daemon exposes project knowledge and mutations over HTTP, a browser UI, and an MCP server. Projects can define argon2id-hashed keys in project.ncl (with role admin|viewer and an audit label). Prior to this ADR, the UI login flow set a session cookie but the REST API accepted raw passwords as Bearer tokens on every request — each call paying ~100ms for argon2 verification. The CLI had no Bearer support at all; project-add and project-remove called the daemon without credentials. The daemon manage page had no admin identity concept. There was no way to enumerate or revoke active sessions.", + + decision = "All surfaces exchange a raw key once via POST /sessions for a UUID v4 bearer token (30-day lifetime, in-memory SessionStore). The session token is used for all subsequent calls — O(1) DashMap lookup. Sessions carry a stable public id (distinct from the bearer token) for safe list/revoke operations without leaking credentials. Project keys have a label field for audit trail. Daemon-level admin uses a separate argon2id hash (ONTOREF_ADMIN_TOKEN_FILE preferred over ONTOREF_ADMIN_TOKEN) and creates sessions under virtual slug '_daemon'. The CLI injects ONTOREF_TOKEN as Authorization: Bearer automatically via bearer-args in store.nu. Key rotation (PUT /projects/{slug}/keys) revokes all active sessions for the rotated project. GET /sessions and DELETE /sessions/{id} implement two-tier visibility: project admin sees own project sessions; daemon admin sees all.", + + rationale = [ + { + claim = "Token exchange eliminates per-request argon2 cost", + detail = "argon2id verification takes ~100ms by design (brute-force resistance). Verifying on every CLI invocation, MCP tool call, or UI AJAX request is prohibitive. A UUID v4 session token turns auth into a DashMap::get — O(1), sub-microsecond. The argon2 cost is paid exactly once per login.", + }, + { + claim = "session.id != bearer token prevents session enumeration attacks", + detail = "The list endpoints (GET /sessions) return SessionView with a stable public id. If the bearer token were exposed in list responses, a project admin could steal another admin's token and impersonate them. The id field is a second UUID v4 — safe to expose, useless as a credential.", + }, + { + claim = "is_uuid_v4 fast-path preserves backward compatibility", + detail = "check_primary_auth tries the session token path first (UUID v4 format check is a length + byte comparison, ~10ns). If the bearer is not UUID-shaped, it falls through to argon2 password verification. Existing integrations using raw passwords continue to work without change.", + }, + { + claim = "Virtual '_daemon' slug isolates admin sessions from project sessions", + detail = "Daemon admin sessions are not scoped to any real project. The '_daemon' slug cannot collide with real project slugs (kebab-case, no leading underscore). AdminGuard checks for Role::Admin across any registered project or '_daemon', giving the manage page a clean auth boundary without coupling it to any specific project.", + }, + { + claim = "ONTOREF_ADMIN_TOKEN_FILE preferred over inline env var", + detail = "An inline hash in ONTOREF_ADMIN_TOKEN appears in `ps aux` output and shell history. Reading from a file (ONTOREF_ADMIN_TOKEN_FILE) avoids both surfaces. The file contains only the argon2id hash string; the actual password never touches the daemon process env.", + }, + { + claim = "Key rotation invalidates sessions atomically", + detail = "When keys are rotated, all in-flight sessions for that project authenticated against the old key set are immediately invalid. revoke_all_for_slug scans the DashMap and removes matching entries including the id_index. Actor sessions (ontoref-daemon actors registry) are also invalidated. The UI will receive 401 on next request and redirect to login.", + }, + ], + + consequences = { + positive = [ + "REST API, MCP, and UI all use the same session token — single auth model, no surface-specific logic", + "CLI project-add, project-remove, and notify-daemon-* calls carry credentials automatically when ONTOREF_TOKEN is set", + "Session list gives admins visibility into who is connected to their project", + "Revocation is O(1) via id_index secondary index; bulk revocation on key rotation is O(active sessions for slug)", + "Daemon admin and project admin are cleanly separated — '_daemon' sessions cannot access project-scoped endpoints and vice versa", + ], + negative = [ + "Sessions are in-memory only — daemon restart requires all clients to re-authenticate", + "30-day token lifetime means a leaked token is valid for up to 30 days unless explicitly revoked", + "ONTOREF_TOKEN in shell env is visible to child processes — use a secrets manager or short-lived token refresh for automated pipelines", + ], + }, + + alternatives_considered = [ + { + option = "Verify argon2 on every Bearer request (no session concept)", + why_rejected = "~100ms per request is unacceptable for CLI invocations (each command is a new HTTP call) and MCP tool sequences (multiple calls per agent turn). Session token lookup is sub-microsecond.", + }, + { + option = "Axum middleware for auth instead of per-handler check_primary_auth", + why_rejected = "Middleware applies uniformly to all routes. ontoref-daemon coexists auth-enabled and open projects on the same router — some endpoints are always public (health, POST /sessions itself), some require project-scoped auth, some require daemon admin. Per-handler checks encode these rules explicitly; middleware would require complex route exemption logic.", + }, + { + option = "Expose bearer token in session list responses", + why_rejected = "GET /sessions is accessible to project admins. Exposing the bearer would allow any project admin to impersonate any other session holder. The public session.id is a safe substitute for revocation targeting.", + }, + { + option = "Separate token store per project", + why_rejected = "A single DashMap keyed by token with a slug field in SessionEntry is sufficient. A secondary id_index DashMap gives O(1) revoke-by-id. Per-project sharding would add complexity without benefit given the expected session count (tens, not millions).", + }, + { + option = "JWT instead of opaque UUID v4 tokens", + why_rejected = "JWTs are self-contained and cannot be revoked without a denylist. Opaque tokens enable instant revocation (key rotation, logout, admin force-revoke) with O(1) lookup. The daemon is local-only — there is no distributed verification scenario that would justify JWT complexity.", + }, + ], + + constraints = [ + { + id = "session-token-never-in-list-response", + claim = "GET /sessions responses must never include the bearer token, only the public session id", + scope = "crates/ontoref-daemon/src/session.rs, crates/ontoref-daemon/src/api.rs", + severity = 'Hard, + check_hint = "rg 'SessionView' crates/ontoref-daemon/src/session.rs — verify no 'token' field exists in SessionView", + rationale = "Exposing bearer tokens in list responses would allow admins to impersonate other sessions. The session.id field is a second UUID v4, safe to expose.", + }, + { + id = "post-sessions-no-auth-required", + claim = "POST /sessions must not require authentication — it is the credential exchange endpoint", + scope = "crates/ontoref-daemon/src/api.rs", + severity = 'Hard, + check_hint = "rg -A5 'route.*sessions.*post' crates/ontoref-daemon/src/api.rs — must not call require_session or check_primary_auth", + rationale = "Requiring auth to obtain auth is a bootstrap deadlock. Rate-limiting on failure is the correct mitigation, not pre-authentication.", + }, + { + id = "key-rotation-must-invalidate-sessions", + claim = "PUT /projects/{slug}/keys must call revoke_all_for_slug before persisting new keys", + scope = "crates/ontoref-daemon/src/api.rs", + severity = 'Hard, + check_hint = "rg 'revoke_all_for_slug' crates/ontoref-daemon/src/api.rs — must appear in project_update_keys handler", + rationale = "Sessions authenticated against the old key set become invalid after rotation. Failing to revoke them would leave stale sessions with elevated access.", + }, + { + id = "bearer-args-injected-by-cli", + claim = "All CLI HTTP calls to the daemon must use bearer-args from store.nu — no hardcoded curl without auth args", + scope = "reflection/modules/store.nu, reflection/bin/ontoref.nu", + severity = 'Soft, + check_hint = "rg 'curl -sf' reflection/bin/ontoref.nu — every occurrence should use ...(bearer-args) or http-get/http-post-json/http-delete helpers", + rationale = "ONTOREF_TOKEN is the single credential source for CLI. Direct curl without bearer-args bypasses the auth model silently.", + }, + ], + + related_adrs = ["adr-002-daemon-for-caching-and-notification-barrier"], + + ontology_check = { + decision_string = "unified key-to-session token exchange; opaque UUID v4 bearer; session.id != token; revocation on key rotation", + invariants_at_risk = ["no-enforcement"], + verdict = 'RequiresJustification, + }, + + invariant_justification = { + invariant = "no-enforcement", + claim = "Auth is opt-in per project. A project with no keys configured runs in open mode — all endpoints accessible without credentials. The protocol itself never mandates auth.", + mitigation = "The no-enforcement axiom applies at the protocol level. Auth is a project-level operational decision, not a protocol constraint. Open deployments (no keys) pass through all auth checks unconditionally.", + }, +} diff --git a/assets/logo_prompt.md b/assets/logo_prompt.md new file mode 100644 index 0000000..e3147c4 --- /dev/null +++ b/assets/logo_prompt.md @@ -0,0 +1,119 @@ +Design a minimalist technical logo for a protocol project called “Ontoref”. + +Ontoref is a self-describing ontology and reflection protocol for evolving codebases. +It models how software projects describe their structure and observe their own evolution using directed acyclic graphs (DAGs). + +Tagline: +“Structure that remembers why.” + +⸻ + +Concept to visualize + +The logo should express a balanced duality between: + +Ontology + • structure + • nodes + • invariants + • static form + • what is + +Reflection + • flow + • edges + • transitions + • evolution + • what becomes + +The symbol should feel like structure and reflection at the same time. + +Examples of visual metaphors: + • a graph node that mirrors itself + • a minimal DAG structure with reflective symmetry + • a directed edge suggesting motion and observation + • negative space completing the structure + +The mark should imply a system that observes itself. + +⸻ + +Style + • minimalist + • geometric + • flat vector logo + • strong silhouette + • balanced symmetry + • technical and precise + • no decorative elements + • readable at small sizes (16–32px) + +The logo must feel like infrastructure software, not a startup brand. + +⸻ + +Symbol constraints + • abstract DAG-inspired symbol + • maximum 2–4 nodes + • suggest directed flow + • clean geometric shapes + • avoid literal graph diagrams + • reflection implied via symmetry or mirroring + +Avoid: + • neural networks + • brain imagery + • infinity loops + • cloud / SaaS icons + • gradients + • complex network diagrams + • mascots + +⸻ + +Color palette + +Dark primary version: + +background: deep slate / near-black (#0D1117 range) + +Accent color (choose one): + • deep teal + • indigo-violet + • amber + +Maximum two colors + background. + +Must also work in monochrome. + +⸻ + +Typography + +Wordmark: Ontoref + +Style: + • geometric monospace or semi-monospace + • technical, calm, restrained + • similar spirit to developer tools and programming languages + +Optional tagline under the wordmark. + +⸻ + +Deliver + • symbol + wordmark + • symbol-only version + • dark background version + • light background version + • clean vector style + +⸻ + +Generate three distinct logo directions: + 1. Mirror Node — a node reflecting itself through symmetry or negative space + 2. Acyclic Flow — minimal DAG structure with a directional edge suggesting motion + 3. Reflective Structure — geometric symbol where half implies structure and half implies reflection + +Flat vector logo, precise geometry, minimal tech aesthetic. +vector logo, flat design, minimal geometry, centered symbol, white space, professional software project identity diff --git a/assets/presentation/.gitignore b/assets/presentation/.gitignore new file mode 100644 index 0000000..5ba5d01 --- /dev/null +++ b/assets/presentation/.gitignore @@ -0,0 +1,23 @@ +node_modules/ +dist/ +.DS_Store +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.env +.env.local +.env.*.local +pnpm-lock.yaml +yarn.lock +package-lock.json +.vscode/ +.idea/ +*.swp +*.swo +*~ +.cache/ +.nuxt/ +build/ +.output/ +coverage/ diff --git a/assets/presentation/.tmp-check-slidev-count.mjs b/assets/presentation/.tmp-check-slidev-count.mjs new file mode 100644 index 0000000..49a99ed --- /dev/null +++ b/assets/presentation/.tmp-check-slidev-count.mjs @@ -0,0 +1,11 @@ +import { resolve } from 'node:path' +import { parser } from '@slidev/cli' + +const data = await parser.load(resolve('.'), resolve('slides.md')) +console.log('slides:', data.slides.length) +for (let i = 0; i < data.slides.length; i++) { + const s = data.slides[i] + const first = (s.content || '').split(/\r?\n/).find(l => l.trim() && !l.trim().startsWith('/g, ' ') + t = t.replace(//gi, ' ') + t = t.replace(/```[\s\S]*?```/g, ' [code example] ') + t = t.replace(/<[^>]+>/g, ' ') + t = t.replace(/^\s*::.*::\s*$/gm, ' ') + t = t.replace(/^\s*#\s*/gm, '') + t = t.replace(/^\s*[-*]\s+/gm, '') + t = t.replace(/^\s*\d+\.\s+/gm, '') + t = t.replace(/\[(.*?)\]\((.*?)\)/g, '$1') + t = t.replace(/[`*_~]/g, '') + t = t.replace(/\|/g, ' ') + t = t.replace(/\n{3,}/g, '\n\n') + const lines = t.split(/\r?\n/).map(l => l.trim()).filter(Boolean) + return lines +} + +function simplify(line) { + let t = line + const repl = [ + [/\binfrastructure\b/gi, 'infra'], + [/\bconfiguration\b/gi, 'config'], + [/\bvalidation\b/gi, 'check'], + [/\breliability\b/gi, 'stable work'], + [/\bdeclarative\b/gi, 'declared'], + [/\borchestrator\b/gi, 'controller'], + [/\bdistributed\b/gi, 'spread out'], + [/\bparadigm\b/gi, 'approach'], + [/\bpragmatism\b/gi, 'practical work'], + [/\bguarantees\b/gi, 'safety rules'], + [/\bprovisioning\b/gi, 'setup'], + [/\bcompiler\b/gi, 'compiler check'], + ] + for (const [re, to] of repl) t = t.replace(re, to) + t = t.replace(/\bIaC\b/g, 'I A C').replace(/\bCI\/CD\b/g, 'C I slash C D').replace(/24×7×365/g, 'all day, every day') + t = t.replace(/\s+—\s+/g, '. ').replace(/\s+->\s+/g, '. then ').replace(/,\s*/g, '. ') + t = t.replace(/\s{2,}/g, ' ').trim() + return t +} + +function say(line) { + const rules = [ + [/\bInfrastructure\b/gi, 'Infrastructure (IN-fruh-STRUHK-cher)'], + [/\bautomation\b/gi, 'automation (aw-tuh-MAY-shun)'], + [/\bconfiguration\b/gi, 'configuration (kun-fig-yuh-RAY-shun)'], + [/\bprovisioning\b/gi, 'provisioning (pruh-VIH-zhuh-ning)'], + [/\bcompiler\b/gi, 'compiler (kum-PIE-ler)'], + [/\breliability\b/gi, 'reliability (rih-lai-uh-BIH-luh-tee)'], + [/\bvalidation\b/gi, 'validation (val-ih-DAY-shun)'], + [/\bdeclarative\b/gi, 'declarative (dih-KLAR-uh-tiv)'], + [/\borchestrator\b/gi, 'orchestrator (OR-kes-tray-ter)'], + [/\bparadigm\b/gi, 'paradigm (PAIR-uh-dym)'], + [/\bRust\b/g, 'Rust (rahst)'], + [/\bSerde\b/g, 'Serde (SER-dee)'], + [/\bOption\b/g, 'Option (OP-shun)'], + [/\benum\b/gi, 'enum (EE-num)'], + [/\bKubernetes\b/gi, 'Kubernetes (koo-ber-NET-eez)'], + [/\bAnsible\b/gi, 'Ansible (AN-suh-buhl)'], + [/\bTerraform\b/gi, 'Terraform (TEH-ruh-form)'], + [/\bCI\/CD\b/g, 'CI/CD (see-eye / see-dee)'], + [/\bIaC\b/g, 'IaC (eye-ay-see)'], + ] + let t = line + for (const [re, rep] of rules) t = t.replace(re, rep) + t = t.replace(/\s+—\s+/g, ' / ').replace(/\.\s+/g, '. / ').replace(/:\s+/g, ': / ') + return t +} + +function phase(n) { + if (n <= 3) return ['Act 1 - Hook & Promise', 'Low -> Medium', 'Credibilidad y dolor real', 'Calma, pausas limpias'] + if (n <= 7) return ['Act 2 - Escalation', 'Medium -> High', 'Crecimiento de complejidad sin control', 'Ritmo creciente'] + if (n <= 14) return ['Act 3 - Problem Anatomy', 'High', 'Causa raiz y costo de fallar tarde', 'Didactico, frases cortas'] + if (n <= 18) return ['Act 4 - Turning Point', 'Peak', 'No faltan tools, falta paradigma', 'Silencios intencionales'] + if (n <= 23) return ['Act 5 - Resolution', 'High -> Medium', 'Types y compiler como respuesta', 'Claro y tecnico'] + if (n <= 31) return ['Act 6 - Proof in Production', 'Medium -> High', 'Casos reales e impacto operativo', 'Energetico y concreto'] + return ['Act 7 - Close & CTA', 'Medium -> Low', 'Cierre emocional con accion', 'Lento y memorable'] +} + +const slideTexts = slides.map(s => ({ ...s, lines: stripText(s.raw) })) + +// 1) reader-script-en.md +let out = '# Rustikon 2026 - Slide Text (Reader Version)\n\n' +out += 'One section per real Slidev slide (parser-based count).\n\n' +for (const s of slideTexts) { + out += `## Slide ${s.n}${s.name ? ` (name: ${s.name})` : ''}\n\n` + const [story, tension, emphasis, delivery] = phase(s.n) + out += `STORY: ${story}\nTENSION: ${tension}\nEMPHASIS: ${emphasis}\nDELIVERY: ${delivery}\n\n` + if (!s.lines.length) out += '[No readable text]\n\n' + else out += s.lines.join('\n') + '\n\n' +} +fs.writeFileSync('reader-script-en.md', out) + +// 2) pronunciation +out = '# Rustikon 2026 - Reader Script with Pronunciation Guide\n\n' +out += 'Use this for practice. Read the SAY line.\n\n' +for (const s of slideTexts) { + out += `## Slide ${s.n}${s.name ? ` (name: ${s.name})` : ''}\n\n` + const [story, tension, emphasis, delivery] = phase(s.n) + out += `STORY: ${story}\nTENSION: ${tension}\nEMPHASIS: ${emphasis}\nDELIVERY: ${delivery}\n\n` + if (!s.lines.length) { + out += 'EN: [No readable text]\nSAY: [No readable text]\n\n' + continue + } + for (const l of s.lines) out += `EN: ${l}\nSAY: ${say(l)}\n\n` +} +fs.writeFileSync('reader-script-en-pronunciation.md', out) + +// 3) simple +out = '# Rustikon 2026 - Simple Reader Script (Easy English)\n\n' +out += 'Short and clear lines for easy pronunciation.\n\n' +for (const s of slideTexts) { + out += `## Slide ${s.n}${s.name ? ` (name: ${s.name})` : ''}\n\n` + const [story, tension, emphasis, delivery] = phase(s.n) + out += `STORY: ${story}\nTENSION: ${tension}\nEMPHASIS: ${emphasis}\nDELIVERY: ${delivery}\n\n` + if (!s.lines.length) { + out += '[No readable text]\n\n' + continue + } + for (const l of s.lines) out += `${simplify(l)}\n\n` +} +fs.writeFileSync('reader-script-en-simple.md', out) + +// 4) live +out = '# Rustikon 2026 - Live Reader Script (Speech Only)\n\n' +out += 'Only lines to speak. Legend: `/` short pause, `//` long pause.\n\n' +for (const s of slideTexts) { + out += `## Slide ${s.n}${s.name ? ` (name: ${s.name})` : ''}\n\n` + const [story, tension, emphasis, delivery] = phase(s.n) + out += `STORY: ${story}\nTENSION: ${tension}\nEMPHASIS: ${emphasis}\nDELIVERY: ${delivery}\n\n` + const spoken = s.lines.map(simplify).filter(l => /[A-Za-z]/.test(l) && !/^[-=]+$/.test(l)) + if (!spoken.length) { + out += '(No spoken text. Move forward.)\n\n' + continue + } + for (const l of spoken) out += `${l.replace(/\.\s+/g, '. / ').replace(/\s+—\s+/g, ' / ')}\n\n` +} +fs.writeFileSync('reader-script-en-live.md', out) + +// 5) live 30-min +const weights = slideTexts.map(s => { + const words = s.lines.join(' ').split(/\s+/).filter(Boolean).length + return 6 + words +}) +let secs = weights.map(w => Math.round((w / weights.reduce((a,b)=>a+b,0)) * TOTAL_SECONDS)) +const minSec=15,maxSec=90 +secs = secs.map(v => Math.max(minSec, Math.min(maxSec, v))) +let diff = TOTAL_SECONDS - secs.reduce((a,b)=>a+b,0) +while (diff !== 0) { + let changed = false + for (let i=0; i 0 && secs[i] < maxSec) { secs[i]++; diff--; changed = true } + else if (diff < 0 && secs[i] > minSec) { secs[i]--; diff++; changed = true } + } + if (!changed) break +} +const mmss = (t)=>`${String(Math.floor(t/60)).padStart(2,'0')}:${String(t%60).padStart(2,'0')}` +out = '# Rustikon 2026 - Live Reader (30-Min Timing)\n\n' +out += 'Total target: 30:00 (1800s)\n\n' +out += 'How to use:\n- Keep your pace near each slide target.\n- If you are late, cut one example line and move on.\n- If you are early, add one short emphasis line.\n\n' +let cum = 0 +for (let i=0;i /[A-Za-z]/.test(l) && !/^[-=]+$/.test(l)) + if (!spoken.length) out += '(No spoken text. Move forward.)\n\n' + else for (const l of spoken) out += `${l.replace(/\.\s+/g, '. / ')}\n\n` +} +out += '## Checkpoints\n\n' +for (const mark of [5,10,15,20,25,30]) { + const target = mark * 60 + let c = 0, slideN = 1 + for (let i=0;i= target) { slideN = i + 1; break } } + out += `- ${String(mark).padStart(2,'0')}:00 -> around Slide ${slideN}\n` +} +fs.writeFileSync('reader-script-en-live-30min.md', out) + +// 6) key moments for 35 slides +const key = `# Rustikon 2026 - Key Story Moments\n\nUse this as your minimal narrative map for a 30-minute talk.\n\n## Moment 1 - Hook (Slides 1-3)\nPurpose: Win attention and establish credibility.\n\nAnchor line:\n"I have lived this problem for decades, and I wanted one thing: to sleep."\n\n## Moment 2 - Rising Tension (Slides 4-7)\nPurpose: Show complexity growing faster than control.\n\nAnchor line:\n"We changed tools many times, but not the model."\n\n## Moment 3 - Crisis / Root Cause (Slides 8-14)\nPurpose: Make the cost of late failure undeniable.\n\nAnchor line:\n"Fail late is expensive. Fail early is cheap."\n\n## Moment 4 - Turning Point (Slides 15-18)\nPurpose: Reframe the problem.\n\nAnchor line:\n"The problem was not the tools. The problem was the paradigm."\n\n## Moment 5 - Proof in Production (Slides 24-31)\nPurpose: Show real operational evidence.\n\nAnchor line:\n"At 3 AM, the system responds first. Not me."\n\n## Moment 6 - Emotional Close + CTA (Slides 33-35)\nPurpose: Close with trust and clear action.\n\nAnchor line:\n"Rust gave me deterministic systems and better sleep."\n\n## One-Line Backup Version\n1. I lived this problem for decades.\n2. Complexity grew; certainty did not.\n3. Late failures are the real tax.\n4. Types changed the paradigm.\n5. Production proof: lower MTTR and deterministic workflows.\n6. Start small: model infrastructure as types.\n` +fs.writeFileSync('key-moments-storytelling.md', key) + +console.log('Real Slidev slides:', slideTexts.length) +console.log('Docs regenerated with parser-based slide count.') diff --git a/assets/presentation/00_talk-structure.md b/assets/presentation/00_talk-structure.md new file mode 100644 index 0000000..fb1262b --- /dev/null +++ b/assets/presentation/00_talk-structure.md @@ -0,0 +1,215 @@ +# Talk Structure: Why I Needed Rust + +> Orientado a: `abstract_en.md` +> Fuente: `2026-02-17-notas_voz.md` + +--- + +## Sistema de Medidores *(elemento visual persistente)* + +Tres barras que aparecen en esquina de cada slide de transición. +Escala 0–5. Se actualizan en cada etapa clave. + +``` +🛡 Confianza ●●●●● = máxima certeza sobre el sistema +😴 Descanso ●●●●● = puedes dormir tranquilo +🔥 Estrés ●●●●● = alarma permanente, pesadillas +``` + +**Estado inicial (apertura)** +``` +🛡 ●●●○○ 😴 ●●●○○ 🔥 ●○○○○ +``` +*Se presenta como "¿Reconoces estos números? Sigamos..."* + +**Progresión a lo largo de la charla:** + +| Momento | 🛡 Confianza | 😴 Descanso | 🔥 Estrés | +|---|:---:|:---:|:---:| +| Etapa 1 — Local | ●●●●○ | ●●●●○ | ●○○○○ | +| Etapa 2 — Redes | ●●●○○ | ●●●○○ | ●●○○○ | +| Etapa 3 — Cloud/CI-CD | ●●○○○ | ●○○○○ | ●●●●○ | +| YAML hell / peak pain | ●○○○○ | ○○○○○ | ●●●●● | +| Rust — compilador | ●●●○○ | ●●○○○ | ●●●○○ | +| Rust — tipos + traits | ●●●●○ | ●●●●○ | ●●○○○ | +| Cierre | ●●●●● | ●●●●● | ●○○○○ | + +> El título de la charla ES el último estado del medidor de sueño. + +--- + +## 0. Hook de apertura + +**Imagen concreta**: definir un host, un nodo, un puerto. +- Eso es todo. Queremos que algo corra en algún sitio y que algo pueda hablar con él. +- ¿Cómo de difícil puede ser? + +**Promesa**: vamos a ver por qué eso, durante 30 años, nos ha dado pesadillas. Y por qué Rust cambió eso. + +*→ Mostrar medidores en interrogante o en estado inicial neutro* + +--- + +## 1. Evolución de los desafíos en infraestructura (2013-2025) + +*Abstract: "The evolution of infrastructure challenges (2013-2025)"* + +### Etapa 1 — Local (finales 80s / primeros 90s) +- Terminales tontos, desarrollo local +- Ciclos de despliegue largos, baja urgencia +- Un solo estado, fácil de observar y controlar +- IaC: scripts procedurales, lógica oculta en comandos o en la aplicación + +> *Medidores fin Etapa 1:* `🛡 ●●●●○ 😴 ●●●●○ 🔥 ●○○○○` +> *"Tienes un servidor. Sabes lo que tiene. Puedes dormir."* + +### Etapa 2 — Conectividad en red / Internet +- Los sistemas están cada vez más lejos — acceso remoto, redes distantes +- Seguridad empieza a importar; procesos críticos, coste de caída sube +- Más agentes participando (dev, ops, seguridad) — hay que armonizar +- Armonizar: instalación de paquetes, configuración de recursos, actualizaciones en paralelo +- IaC: procesos automatizables y reproducibles, primeros intentos declarativos, config-driven model + +> *Medidores fin Etapa 2:* `🛡 ●●●○○ 😴 ●●●○○ 🔥 ●●○○○` +> *"Más piezas. Más personas. Empieza a ponerse interesante."* + +### Etapa 3 — Contenedores / Cloud / CI-CD +- Monolítico → distribuido, 24×7×365, alta disponibilidad +- Cloud, híbrido, multi-cloud, on-prem — todo a la vez +- Rollback y rollforward: como transacciones de BD, pero en infraestructura +- Escalado horizontal **y** vertical, también **desescalado** — en ambas direcciones +- CI/CD continuo, ciclos cortos — nuevas funcionalidades, nuevos despliegues, permanente +- IaC: Helm, Ansible, Terraform — más herramientas, más complejidad +- Múltiples versiones de "lo que creemos que es válido" — la fuente de verdad se fragmenta +- **Resultado**: estados de alarma como norma, no como excepción + +> *Medidores fin Etapa 3:* `🛡 ●●○○○ 😴 ●○○○○ 🔥 ●●●●○` + +> *"¿Hemos aumentado la productividad? Sí.
¿Hemos aumentado el estrés? Sí.
¿Hemos aumentado las posibilidades de tener problemas? También.
¿Tenemos más control y seguridad? No."* + +--- + +## 2. Por qué el IaC tradicional falla a escala + +*Abstract: "Why traditional approaches to IaC fall short at scale"* + +### El ciclo del YAML hell +1. JSON entre máquinas funciona bien → pero es ilegible para humanos +2. Pasamos a YAML / TOML → sintaxis frágil, errores de indentación +3. Usamos gestores de plantillas (Helm, Jinja) → generan YAML a partir de variables en YAML +4. **¿Valida el contenido?** No. En absoluto. +5. Cruzamos los dedos. + +> *Medidores — YAML hell peak:* `🛡 ●○○○○ 😴 ○○○○○ 🔥 ●●●●●` +> *"Esto es el suelo. A partir de aquí, o seguimos igual o buscamos algo mejor."* + +### Las tres preguntas sin respuesta + +**Pregunta 1: ¿Por qué esperamos a que se rompa?** +- "In my machine it works" — en producción, no lo sé +- Fail late = coste máximo +- Queremos: fail fast, fail cheap + +**Pregunta 2: ¿Tenemos claro lo que queremos?** +- ¿La declaración es suficiente? ¿Consistente con lo que es posible? +- ¿Respeta los límites y reglas del sistema? +- ¿Cuál es la "fuente de verdad" y cuándo muta? + +**Pregunta 3: ¿Tenemos herramientas que garanticen determinismo?** +- CI/CD continuo sin validación semántica = esperanza continua +- Queremos certeza, no aleatoriedad +- "En mi máquina funciona" no puede ser el estándar de producción + +### Analogía del restaurante *(hilo conductor)* +| Restaurante | Infraestructura | +|---|---| +| Comensal declara lo que quiere | Config declarativa (YAML, HCL) | +| Camarero valida si es posible | Orchestrator (K8s, Ansible) | +| Cocina ejecuta y entrega | Runtime / provisioning | +| El plato llega o no llega | Deployment falla o no | + +- El camarero no puede volver a cocina con un pedido imposible +- La cocina no puede asumir ingredientes que no están +- Cada actor en la cadena tiene una vista parcial de "la verdad" +- Un fallo en mesa ≠ un fallo en cocina ≠ un fallo en entrega → coste diferente en cada punto + +--- + +## 3. Type safety y memory safety como fiabilidad en producción + +*Abstract: "Type safety and memory safety as production reliability"* + +### Lo que Rust introduce + +**Tipado estático estricto** +- Un número no es un string. Un puerto no es cualquier entero. +- Tipos primarios ricos + tipos compuestos (structs, enums) +- Las conversiones (cast) son explícitas, no implícitas + +**Inmutabilidad por defecto** +- "Quiero tomate. Puedes inventarte lo que quieras, pero sin tomate no es el plato que pedí." +- Lo que no puede cambiar, no cambia. Sin sorpresas en runtime. + +**Options, no nulos** +- No `null`, no "asume que está". `Option`: está o no está, y ambos casos son manejados. +- Ingrediente opcional: si no está, lo sé. Lo cobro o no. Pero lo controlo. + +**Enums y rangos como restricciones** +- Un puerto válido es un rango entre 0 y 65535, no cualquier número +- Un cloud provider es `AWS | GCP | Azure | OnPrem`, no un string libre +- Las restricciones son parte del tipo, no de la documentación + +**Traits como contratos** +- Composición en vez de herencia +- Implementaciones validadas contra interfaces definidas +- Diferentes "actores" en la cadena pueden tener diferentes implementaciones del mismo contrato + +**El compilador como pre-validación** +- Valida *antes* de construir el binario +- No cuando lleva tiempo en ejecución +- No cuando se llama a una función que hace meses que nadie toca +- Binarios predecibles en comportamiento de memoria, recursos y flujos + +> *"El compilador es el camarero que valida el pedido antes de que llegue a cocina."* + +> *Medidores — Rust completo:* `🛡 ●●●●● 😴 ●●●●● 🔥 ●○○○○` + +--- + +## 4. Orquestación segura multi-cloud y on-prem + +*Abstract: "Building safe orchestration across multi-cloud and on-prem environments"* + +*(Sección a desarrollar — no cubierta en las notas de voz actuales)* + +Puntos a cubrir: +- Tipos como esquema de infraestructura → no YAML libre +- Traits para abstraer providers (AWS, GCP, on-prem, K8s) +- Compilador detecta configuraciones imposibles antes del despliegue +- Estado mutable explícito → sin configuration drift silencioso + +--- + +## 5. Aplicaciones reales + +*Abstract: "Real applications: Kubernetes, blockchain validators, disaster recovery"* + +*(Sección a desarrollar — no cubierta en las notas de voz actuales)* + +Casos concretos: +- Kubernetes: operadores en Rust, CRDs con tipos seguros +- Blockchain validators: disponibilidad crítica, configuración determinista +- Disaster recovery: reproducibilidad, sin "funciona en prod pero no en DR" + +--- + +## Cierre + +- No hemos inventado nada nuevo. Los problemas siempre han existido. +- Rust los reúne y los resuelve: tipos, traits, compiler, memory safety. +- El resultado: infraestructura predecible. Deployments sin pesadillas. +- **Duermes bien.** + +> *Medidores — cierre:* `🛡 ●●●●● 😴 ●●●●● 🔥 ●○○○○` +> +> *Slide final: los tres medidores en verde. Título de la charla. Fin.* diff --git a/assets/presentation/01_talk-structure.md b/assets/presentation/01_talk-structure.md new file mode 100644 index 0000000..f56650e --- /dev/null +++ b/assets/presentation/01_talk-structure.md @@ -0,0 +1,534 @@ +# Talk Structure: Why I Needed Rust + +> Orientado a: `abstract_en.md` +> Fuente: `2026-02-17-notas_voz.md` + +--- + +## Sistema de Medidores *(elemento visual persistente)* + +Tres barras que aparecen en esquina de cada slide de transición. +Escala 0–5. Se actualizan en cada etapa clave. + +``` +🛡 Confianza ●●●●● = máxima certeza sobre el sistema +😴 Descanso ●●●●● = puedes dormir tranquilo +🔥 Estrés ●●●●● = alarma permanente, pesadillas +``` + +**Estado inicial (apertura)** +``` +🛡 ●●●○○ 😴 ●●●○○ 🔥 ●○○○○ +``` +*Se presenta como "¿Reconoces estos números? Sigamos..."* + +**Progresión a lo largo de la charla:** + +| Momento | 🛡 Confianza | 😴 Descanso | 🔥 Estrés | +|---|:---:|:---:|:---:| +| Etapa 1 — Local | ●●●●○ | ●●●●○ | ●○○○○ | +| Etapa 2 — Redes | ●●●○○ | ●●●○○ | ●●○○○ | +| Etapa 3 — Cloud/CI-CD | ●●○○○ | ●○○○○ | ●●●●○ | +| YAML hell / peak pain | ●○○○○ | ○○○○○ | ●●●●● | +| Rust — compilador | ●●●○○ | ●●○○○ | ●●●○○ | +| Rust — tipos + traits | ●●●●○ | ●●●●○ | ●●○○○ | +| Cierre | ●●●●● | ●●●●● | ●○○○○ | + +> El título de la charla ES el último estado del medidor de sueño. + +--- + +## 0. Hook de apertura + +**Imagen concreta**: definir un host, un nodo, un puerto. +- Eso es todo. Queremos que algo corra en algún sitio y que algo pueda hablar con él. +- ¿Cómo de difícil puede ser? + +**Promesa**: vamos a ver por qué eso, durante 30 años, nos ha dado pesadillas. Y por qué Rust cambió eso. + +*→ Mostrar medidores en interrogante o en estado inicial neutro* + +--- + +## 1. Evolución de los desafíos en infraestructura (2013-2025) + +*Abstract: "The evolution of infrastructure challenges (2013-2025)"* + +### Etapa 1 — Local (finales 80s / primeros 90s) +- Terminales tontos, desarrollo local +- Ciclos de despliegue largos, baja urgencia +- Un solo estado, fácil de observar y controlar +- IaC: scripts procedurales, lógica oculta en comandos o en la aplicación + +> *Medidores fin Etapa 1:* `🛡 ●●●●○ 😴 ●●●●○ 🔥 ●○○○○` +> *"Tienes un servidor. Sabes lo que tiene. Puedes dormir."* + +### Etapa 2 — Conectividad en red / Internet +- Los sistemas están cada vez más lejos — acceso remoto, redes distantes +- Seguridad empieza a importar; procesos críticos, coste de caída sube +- Más agentes participando (dev, ops, seguridad) — hay que armonizar +- Armonizar: instalación de paquetes, configuración de recursos, actualizaciones en paralelo +- IaC: procesos automatizables y reproducibles, primeros intentos declarativos, config-driven model + +> *Medidores fin Etapa 2:* `🛡 ●●●○○ 😴 ●●●○○ 🔥 ●●○○○` +> *"Más piezas. Más personas. Empieza a ponerse interesante."* + +### Etapa 3 — Contenedores / Cloud / CI-CD +- Monolítico → distribuido, 24×7×365, alta disponibilidad +- Cloud, híbrido, multi-cloud, on-prem — todo a la vez +- Rollback y rollforward: como transacciones de BD, pero en infraestructura +- Escalado horizontal **y** vertical, también **desescalado** — en ambas direcciones +- CI/CD continuo, ciclos cortos — nuevas funcionalidades, nuevos despliegues, permanente +- IaC: Helm, Ansible, Terraform — más herramientas, más complejidad +- Múltiples versiones de "lo que creemos que es válido" — la fuente de verdad se fragmenta +- **Resultado**: estados de alarma como norma, no como excepción + +> *Medidores fin Etapa 3:* `🛡 ●●○○○ 😴 ●○○○○ 🔥 ●●●●○` + +> *"¿Hemos aumentado la productividad? Sí.
¿Hemos aumentado el estrés? Sí.
¿Hemos aumentado las posibilidades de tener problemas? También.
¿Tenemos más control y seguridad? No."* + +--- + +## 2. Por qué el IaC tradicional falla a escala + +*Abstract: "Why traditional approaches to IaC fall short at scale"* + +### La analogía del restaurante *(hilo conductor de toda la charla)* + +Un restaurante tiene tres actores: **comensal** (mesa), **camarero** (servicio), **cocina**. + +| Restaurante | Infraestructura | +|---|---| +| Comensal declara lo que quiere | Config declarativa (YAML, HCL, Helm) | +| Camarero valida y transmite | Orchestrator (K8s, Ansible, Terraform) | +| Cocina ejecuta y entrega | Runtime / provisioning | +| El plato llega — o no | Deployment exitoso — o no | + +**Lo que hace que funcione** — o no: +- El comensal declara, no implementa: no dice *cómo* cocinar, dice *qué* quiere +- El camarero debe saber qué es posible y validar *antes* de ir a cocina +- "Quiero X" → camarero va a cocina → "X no hay, ¿por qué está en la carta?" → vuelve a mesa +- Equivalente: configuré un host con el puerto 8443 → ese puerto no está permitido → hay que reconfigurar desde cero + +**La verdad que muta — y el problema de estado** +- La "verdad" no es estática. Muta en cada paso de la cadena: + - Comensal: su petición oral + - Camarero: la nota que toma + - Cocina: las marcas sobre lo que ya está hecho o no + - Caja: el ticket de pago +- Cada actor ve una *parte* de la verdad, relevante para él en ese momento +- Como en un sistema neuronal: hay eventos que no activan ciertas neuronas porque no son relevantes para ellas +- **El camarero conoce al cliente habitual** ("siempre sin sal") — la cocina, no. ¿Está ese contexto explícito en el pedido, o asumido? +- Si el camarero cambia, o si interviene otra estación de cocina → el estado implícito se pierde + +**El coste del fallo depende de dónde ocurre** +- Fallo en mesa al pedir (pedido imposible): barato — se corrige antes de llegar a cocina +- Fallo en cocina al elaborar (ingrediente no disponible): medio — hay que volver a mesa y renegociar +- Fallo al entregar (plato se cae): caro — hay que rehacer desde cero +- Fallo cuando llega el plato y no es lo que se pidió: muy caro — experiencia destruida + tiempo perdido +- **Fail early = fail cheap. Fail in production = nightmare.** + +**La renegociación: "no tengo champiñón"** +- Un actor en la cadena descubre que no puede cumplir parte del pedido +- Debe volver atrás y validar con el comensal: "¿te pongo verduras?" +- Esto requiere que el cambio sea explícito, trazado, y re-autorizado — no silencioso +- Equivalente en infra: configuration drift, estado divergente sin notificación + +--- + +### El ciclo de la config: cómo llegamos hasta aquí + +1. **Hardcoded** — dependencias, rutas, puertos dentro del binario. Control total, cero flexibilidad +2. **Config externa** (JSON) — funciona bien entre máquinas, ilegible para humanos a escala +3. **YAML / TOML** — más legible, pero sintaxis frágil: indentación, tipos implícitos, errores silenciosos +4. **YAML + Serde** — cargamos la config con Serde, que valida la *estructura*: + - ¿Existe el campo? ¿Es del tipo correcto? + - ¿Aceptamos "elefante" como animal de compañía? Depende del tipo. Pero si el tipo es `String`... sí. + - **Serde valida forma. No valida significado.** +5. **Helm / Jinja templates** — escribimos YAML a partir de variables (en YAML o JSON) + - El template genera el YAML final que consume la aplicación + - ¿Valida el contenido del YAML generado? **No. En absoluto.** + - Como usar un LLM con un markdown de referencia: el formato está, pero ¿el contenido es correcto? Eso no lo garantiza nadie. +6. **Resultado**: CI/CD continuo + config sin validación semántica = **esperanza continua** + +> *"Lo que hacemos es escribir lo que queremos, como una carta a los Reyes Magos. Y cruzamos los dedos."* + +--- + +``` +┌─────────────────────────────────────────────┐ +│ SLIDE STANDALONE — Esperanza continua │ +│ │ +│ CI/CD continuo. │ +│ Sin validación semántica. │ +│ │ +│ Esperanza continua. │ +│ │ +│ (cruzamos los dedos en producción) │ +└─────────────────────────────────────────────┘ +``` +> *Medidores:* `🛡 ●○○○○ 😴 ○○○○○ 🔥 ●●●●●` +> *Tono: irónico, de reconocimiento. La audiencia asiente.* + +--- + +### Las tres preguntas sin respuesta + +**Pregunta 1 — ¿Por qué esperamos a que se rompa?** +- "In my machine it works" — en producción, no lo sé +- Fail late = coste máximo +- Queremos: fail fast, fail cheap — cuanto antes, menor el coste + +**Pregunta 2 — ¿Tenemos claro lo que queremos?** +- ¿La declaración es suficiente y consistente con lo que es posible? +- ¿Respeta los límites del sistema? ¿Cuáles son esos límites — estáticos o dinámicos? +- ¿Cuál es la fuente de verdad? ¿Y cuándo y cómo muta? + +**Pregunta 3 — ¿Podemos garantizar determinismo?** +- ¿Tenemos herramientas que usen nuestras definiciones para operar automáticamente en el ciclo de vida? +- ¿Podemos tener certeza de que obtendremos lo que formulamos — no de forma aleatoria? +- No "en mi máquina funciona". Siempre. En cualquier máquina. En cualquier momento. + +> *En realidad no estamos inventando nada. Todo ya existe. El problema es si lo estamos gestionando adecuadamente.* + +--- + +``` +┌─────────────────────────────────────────────┐ +│ SLIDE STANDALONE — Pesadilla continua │ +│ │ +│ Sistemas que no sabemos cómo controlar. │ +│ Esperamos que funcionen. │ +│ Cuando no funcionan, los arreglamos. │ +│ │ +│ Pesadilla continua. │ +│ │ +│ (el estado de alarma es lo normal) │ +└─────────────────────────────────────────────┘ +``` +> *Medidores:* `🛡 ●○○○○ 😴 ○○○○○ 🔥 ●●●●●` +> *Tono: oscuro, sin ironía. El suelo emocional de la charla. Pausa aquí.* + +--- + +## 3. Type safety y memory safety como fiabilidad en producción + +*Abstract: "Type safety and memory safety as production reliability"* + +### El puente: de YAML+Serde a tipos Rust + +Serde carga config estructuralmente válida. Pero "elefante" como valor de `animal_companía: String` compila. +La respuesta de Rust: **no uses `String`. Usa un tipo.** + +```rust +enum Animal { Perro, Gato, Conejo } // "elefante" no compila +``` + +Eso es lo que cambia. No el formato de la config. El modelo de qué puede contener. + +--- + +### Lo que Rust introduce — respuesta a las tres preguntas + +**Tipado estático estricto** *(responde: "¿tenemos claro lo que queremos?")* +- Un número no es un string. Un puerto no es cualquier entero. +- Los tipos son la declaración de intenciones — sin ambigüedad, sin equívocos +- Las conversiones (cast) son explícitas, no implícitas +- Tipos primarios ricos + structs, enums, tuplas, newtypes + +**Inmutabilidad por defecto** *(responde: "¿qué puede cambiar?")* +- "Quiero tomate. Puedes inventarte lo que quieras, pero sin tomate no es el plato que pedí." +- `let` es inmutable. `let mut` es explícito y deliberado. +- Los invariantes son invariantes — no silenciosamente mutados en runtime + +**`Option`, no nulos** *(responde: "¿qué es opcional y qué es obligatorio?")* +- No `null`, no "asume que está si no se especifica" +- `Option`: o está (`Some`) o no está (`None`) — y ambos casos se manejan explícitamente +- Restaurante: "champiñón opcional. Si no está, lo sé. Lo decido. Lo gestiono." +- El camarero no puede ignorar que no hay champiñón — el compilador no le deja + +**Enums como dominio cerrado** *(responde: "¿cuáles son los valores válidos?")* +- Un cloud provider es `enum Provider { AWS, GCP, Azure, OnPrem }` — no un `String` libre +- Un puerto es un `u16` con rango validado — no cualquier entero +- Las restricciones están en el tipo, no en comentarios, no en documentación, no en la memoria del equipo + +**Traits como contratos entre actores** *(responde: "¿pueden los actores de la cadena cambiar?")* +- Composición en vez de herencia rígida +- Cada "actor" de la cadena (AWS provider, K8s, on-prem) implementa el mismo trait +- La cadena funciona con cualquier implementación que cumpla el contrato +- Como el restaurante: el camarero puede cambiar — si conoce el protocolo, el proceso es el mismo + +**El compilador como el camarero que valida** *(responde: "¿cuándo detectamos el fallo?")* +- Valida **antes** de construir el binario — no en runtime, no en producción +- No cuando lleva horas ejecutándose. No cuando se llama a una función que nadie tocaba hace meses. +- Fail early. Fail cheap. En compilación, no en el 2am del domingo. +- Binarios predecibles: comportamiento de memoria, recursos y flujos deterministas + +> *"El compilador es el camarero que valida el pedido antes de que llegue a cocina. Antes de que el cliente espere. Antes de que el ingrediente no esté."* + +> *Medidores — compilador activo:* `🛡 ●●●○○ 😴 ●●○○○ 🔥 ●●●○○` +> *Medidores — tipos + traits + options:* `🛡 ●●●●○ 😴 ●●●●○ 🔥 ●●○○○` +> *Medidores — Rust completo en producción:* `🛡 ●●●●● 😴 ●●●●● 🔥 ●○○○○` + +--- + +``` +┌─────────────────────────────────────────────┐ +│ SLIDE STANDALONE — Certeza continua │ +│ │ +│ El compilador valida antes del binario. │ +│ Los tipos definen lo que es posible. │ +│ El contrato se cumple — o no compila. │ +│ │ +│ Certeza continua. │ +│ │ +│ (para continuar durmiendo bien) │ +└─────────────────────────────────────────────┘ +``` +> *Medidores:* `🛡 ●●●●● 😴 ●●●●● 🔥 ●○○○○` +> *Tono: resolución. El arco cierra. El título de la charla, demostrado.* + +--- + +## 4. Orquestación segura multi-cloud y on-prem + +*Abstract: "Building safe orchestration across multi-cloud and on-prem environments"* + +### De la teoría al sistema real + +Todo lo anterior no es hipotético. Es la arquitectura de **Provisioning** — un sistema de orquestación de infraestructura en producción, escrito en Rust, que gestiona despliegues en AWS, UpCloud y on-prem desde una sola base de código. + +La pregunta es: ¿cómo se traduce "tipos y traits" a un sistema que maneja multi-cloud real? + +--- + +### Nickel como source of truth tipado (ADR-003) + +YAML fue rechazado explícitamente. TOML fue rechazado. La razón: sin type safety. + +Nickel — un lenguaje de configuración funcional con sistema de tipos — reemplaza YAML: + +```nickel +{ + infrastructure = { + compute | { + region | String, + count | Number & (> 0), + auto_scaling | { + min | Number & (> 0), + max | Number & (>= min), -- el compilador verifica esto + } + } + } +} +``` + +- Validación en tiempo de compilación de la config — no en runtime, no en producción +- Merging jerárquico tipado: defaults → workspace → profile → environment → runtime +- Resultado (ADR-003): **zero configuration type errors en producción** +- Alternativas rechazadas explícitamente: TOML (no type safety), KCL, YAML+validación híbrida + +> *"Serde valida forma. Nickel valida significado. El compilador valida antes del despliegue."* + +--- + +### Traits como contratos de provider (el restaurante, resuelto) + +El problema del restaurante: la cocina puede cambiar. AWS no es UpCloud no es bare metal. +La solución Rust: todos implementan el mismo trait. + +```rust +// El trait es el menú. Los providers son las cocinas. +#[async_trait] +pub trait TaskStorage: Send + Sync { + async fn create_task(&self, task: WorkflowTask) -> StorageResult; + async fn update_task(&self, id: &str, status: TaskStatus) -> StorageResult<()>; + async fn list_tasks(&self, filter: TaskFilter) -> StorageResult>; + async fn audit_operation(&self, op: AuditEntry) -> StorageResult<()>; +} + +// Implementaciones: FilesystemStorage, SurrealDbStorage, MemoryStorage +// El orchestrator no sabe cuál usa. El compilador garantiza que todas cumplen. +``` + +```rust +// Dominios cerrados — no strings libres +enum RollbackStrategy { ConfigDriven, Conservative, Aggressive, Custom { operations: Vec } } +enum DependencyType { Hard, Soft, Optional } +enum TaskStatus { Pending, Running, Completed, Failed, Cancelled } +enum ProviderType { UpCloud, AWS, Local } +``` + +Cuando se añade un nuevo provider: implementa el trait o no compila. No hay forma de olvidarse de un caso. + +--- + +### Orquestación con grafo de dependencias + +El orchestrator construye un DAG (grafo acíclico dirigido) de tareas con detección de ciclos en tiempo de compilación del workflow. + +```rust +pub struct WorkflowConfig { + pub max_parallel_tasks: usize, + pub task_timeout_seconds: u64, + pub fail_fast: bool, // falla rápido, falla barato + pub checkpoint_interval_seconds: u64, +} +``` + +Instalación de Kubernetes como ejemplo — el orchestrator resuelve: +``` +containerd (Hard) → etcd (Hard) → kubernetes → cilium (requires kubernetes) → rook-ceph +``` + +No se puede instalar kubernetes si containerd falla. El tipo `DependencyType::Hard` lo garantiza. +El compilador detecta dependencias circulares. No el operador de guardia a las 3am. + +--- + +### El estado explícito elimina el drift + +El problema de configuration drift: el sistema real diverge silenciosamente del estado deseado. + +Rust obliga a que toda mutación sea explícita (`let mut`). El sistema de estado del orchestrator hace lo mismo a nivel de infraestructura: + +```rust +pub struct WorkflowExecutionState { + pub status: WorkflowStatus, + pub task_states: HashMap, + pub checkpoints: Vec, // qué se hizo y cuándo + pub statistics: WorkflowStatistics, +} +``` + +- Checkpoints cada 5 minutos (configurable) +- Hasta 50 checkpoints retenidos +- Cada estado de provider capturado en `HashMap` +- Sin estado implícito. Sin "el camarero recuerda que el cliente no quiere sal". Está en el pedido. + +> *Medidores — sección 4:* `🛡 ●●●●● 😴 ●●●●● 🔥 ●○○○○` + +--- + +## 5. Aplicaciones reales + +*Abstract: "Real applications: Kubernetes, blockchain validators, disaster recovery"* + +### Kubernetes — despliegue con tipos + +El orchestrator se despliega en Kubernetes. 3 réplicas, rolling update, anti-affinity entre nodos. +Pero lo relevante no es que *corre* en Kubernetes — es cómo se *gestiona* Kubernetes como target. + +El orchestrator provisiona los componentes del cluster como workflow tipado: + +``` +containerd → etcd → kubernetes control plane + → kubelet (workers) + → CoreDNS + → Cilium (CNI, requires kubernetes) + → Rook-Ceph (storage, requires kubernetes + Cilium) +``` + +Cada componente es un tipo. Cada dependencia es un `DependencyType`. El compiler detecta si intentas instalar Cilium sin Kubernetes. No el CI a las 2am. + +Los health probes del orchestrator en K8s — `/health` cada 10 segundos — no son decoración. Son parte del contrato: el sistema sabe cuándo está sano. Lo sabe antes de que falle el cliente. + +--- + +### Blockchain validators — disponibilidad como tipo + +Los validators de blockchain (Polkadot, Ethereum) tienen un requisito brutal: uptime o slashing. Un validator que falla pierde fondos. No hay margen para "en mi máquina funciona". + +Lo que Rust + este sistema aporta: + +**Secretos con post-quantum cryptography (ADR-006)** +- CRYSTALS-Kyber (KEM) + Falcon (signatures) + AES-256-GCM (hybrid) +- Las claves del validator no pueden ser comprometidas por computación cuántica futura +- La rotación de claves es automática, trazada, con audit log + +**SLOs con error budgets reales (ADR-009)** +``` +Tier 1 — Critical: 99.99% uptime = 52.6 minutos de downtime/año +``` +Esto no es un número de marketing. Es un Prometheus rule que dispara alertas y bloquea deploys cuando el burn rate supera el presupuesto. + +**Configuración determinista** +- Los parámetros del validator son tipos, no strings: no puede entrar un valor de `bond_amount` que no sea un `u128` validado +- La config del validator es reproducible bit-a-bit en cualquier nodo — mismo Nickel schema, mismo resultado + +--- + +### Disaster recovery — rollback como tipo, no como procedimiento + +El problema clásico de DR: el runbook existe, pero no se ejecutó correctamente, o el estado en DR no coincide con el de producción. + +**Checkpoints como snapshots de estado completo** +```rust +pub struct Checkpoint { + pub workflow_state: Option, + pub resources: Vec, + pub provider_states: HashMap, +} +``` + +No es "vuelve al commit anterior". Es el estado completo del sistema: qué estaba corriendo, en qué provider, con qué configuración, en qué momento. + +**Rollback como estrategia tipada** +```rust +enum RollbackStrategy { + ConfigDriven, // lo que diga la config + Conservative, // preserva todo salvo lo marcado + Aggressive, // revierte todo + Custom { operations: Vec }, // playbook explícito +} +``` + +No hay forma de hacer rollback sin elegir una estrategia. El compilador no te deja ignorar el caso. + +**Self-healing automatizado (ADR-010)** + +``` +┌─────────────────────────────────────────────┐ +│ SLIDE STANDALONE — MTTR │ +│ │ +│ Sin tipos. Sin compilador. Sin estado. │ +│ │ +│ MTTR > 30 minutos. │ +│ │ +│ (a las ??, apagar el fuego) Alarma pesadilla│ +│ ──────────────────────────────── │ +│ │ +│ Rust. Tipos. Estado explícito. │ +│ Automated response. │ +│ │ +│ MTTR < 5 minutos. │ +│ │ +│ (a las 3am, sin ti) Descanso en paz mental │ +└─────────────────────────────────────────────┘ +``` +> *Medidores:* `🛡 ●●●●● 😴 ●●●●● 🔥 ●○○○○` +> *Tono: cifras reales, no promesas. Pausa después de "sin ti".* + +El `RemediationEngine` ejecuta playbooks tipados: `ScaleService`, `FailoverService`, `RestartService`, `ClearCache`. Si el remedio falla 3 veces, escala a humano. No itera indefinidamente. + +**Multi-backend backup tipado** +- restic, borg, tar, rsync — todos como variantes de un enum +- El backup de producción y el restore en DR usan el mismo tipo, el mismo schema +- "Funciona en prod pero no en DR" no puede ocurrir si el estado es el mismo tipo + +> *"No estamos cruzando los dedos. Tenemos el estado. Tenemos el tipo. Tenemos el rollback. Dormimos."* + +> *Medidores — sección 5:* `🛡 ●●●●● 😴 ●●●●● 🔥 ●○○○○` + +--- + +## Cierre + +- No hemos inventado nada nuevo. Los problemas siempre han existido. +- Rust los reúne y los resuelve: tipos, traits, compiler, memory safety. +- El resultado: infraestructura predecible. Deployments sin pesadillas. +- **Duermes bien.** + +> *Medidores — cierre:* `🛡 ●●●●● 😴 ●●●●● 🔥 ●○○○○` +> +> *Slide final: los tres medidores en verde. Título de la charla. Fin.* diff --git a/assets/presentation/2026-02-17-notas_voz.md b/assets/presentation/2026-02-17-notas_voz.md new file mode 100644 index 0000000..cbcedc9 --- /dev/null +++ b/assets/presentation/2026-02-17-notas_voz.md @@ -0,0 +1,101 @@ +Why I Needed Rust: Finally, Infrastructure Automation I can sleep on +Description + +Esta charla comienza con un problema claro. La definición, por ejemplo, de un host, un nodo, donde se va a ejecutar una aplicación y la forma en la que se va a conectar con ella como un puerto, por ejemplo. Entonces ahora lo que hacemos es un recorrido donde mostramos la evolución de los desafíos en infraestructura en diferentes etapas. + +La primera etapa es cuando no había redes y los desarrollos eran en local. Estamos hablando de finales de los 80, primeros de los 90. Había terminales tontos, cómo se desplegaba una aplicación, cuáles eran los ciclos de despliegue, los tiempos, las urgencias y los modos de despliegue. La siguiente etapa es la que aparece en la conectividad en las redes. Con Internet y la web el efecto conectividad de redes se multiplica, la seguridad se hace más relavante y muchos del os procesos son críticos, se incrementan los costes por errores o caídas de servicios. + +Los sistemas están cada vez más lejos. Hay que conectarse a través de redes remotas o distantes. Los ciclos, el nivel de computación subido. Hay más participación de diferentes agentes en el desarrollo y en la explotación. + +Y hay que armonizar la instalación de paquetes, la configuración de recursos, las actualizaciones y demás. La tercera etapa es con la aparición de los contenedores, pasamos de la computación monolítica a arquitecturas claramente distribuidas, con alta disponibilidad, ya trabajando en entornos 24 x 7, 365 días. Servicios cloud, modos híbridos, sistemas automatizados CD/CI en cíclos contínuos. Infraestructura como código. + +Y queremos ser competitivos con unos ciclos muy cortos de nuevas funcionalidades, de nuevos despliegues, con la posibilidad de hacer lo que hacemos con las transacciones en las bases de datos en épocas anteriores, que es volver hacia atrás y volver hacia adelante. Ya no solo escalamos horizontalmente o verticalmente como ocurría en otras épocas, sino en ambas direcciones y también desecalamos. + +Y los sistemas se hacen cada vez más complejos. La presión es cada vez mayor. Y las psobilidades de descansar y no tener pesadillas aumenta considerablemente y los estados de alarma terminan siendo lo normal, la capacidad de posibilidades de tener problemas sin que ha aumentado enormemente. + +Hemos llegado hasta aquí con lo que conocíamos y teníamos en cada época. Cómo hemos definido las arquitecturas, cómo las hemos construido, planificado, construido y puesto en marcha y cómo hemos trabajado en su mantenimiento cotidiano, en sus actualizaciones y ciclos de cambio. ¿Qué es lo que ocurrido en cada una de estas etapas al respecto? + +En la primera etapa apenas con scripts y con detalles más o menos ocultos en scripts o en definiciones de lo que queríamos de modo procedural. Después una etapa donde hay que instalar varios recursos en paralelo, pues aprendemos e intentamos crear procesos automatizables que se puedan reproducir. Pensamos que podemos escribir lo que queremos en una especie de declaración o una configuración que sido validada. + +Y a partir de la tercera etapa empezamos a poner más herramientas para controlar esto. Pensamos que más es mejor, como siempre. Más computación, más mano de obra, más trabajo. El enfoque aquí es utilizar ya Mele, quizá en algunos casos Tomele como en el caso de Arras. + +Y lo que hacemos es escribir lo que queremos, como una declaración de buenos deseos, como la carta a los reyes o a Papá Noel. Y queremos, cruzamos los dedos con la esperanza de que las cosas se hagan posibles. Monitorizamos, tenemos recursos para hacer seguimiento. Y muchos de los procesos no se repiten hasta que no pasa mucho tiempo en presencia de personas diferentes. + +Entonces empezamos a declarar esto, encontrar fórmulas para declarar esto a través del mundo conocido. Y como JSON no es muy legible para humanos, pues terminamos con Yamele, Tomele y este tipo de cosas. + +Hemos pasado de un estado único que era fácil de mirar y controlar, a una segunda etapa donde había que tener en cuenta varias cosas, podían ocurrir a la vez o podían producirse de forma más o menos paralela, a procesos en la tercera fase ya con contenedores, con infraestructuras cloud, donde todo el proceso es distribuido, donde es muy fácil tener diferentes versiones de lo que creemos que es válido o lo que sería la fuente de verdad. + +Y como esto tiene diferentes perspectivas, diferentes necesidades, con diferentes modos de interpretación, en un mundo de contenedores donde esta manera de distribuir lo que queremos no se hace tan fácil y donde un error tiene resonancia, tiene mucha más repercusión que en las dos primeros etapas. + +¿Hemos aumentado nuestra capacidad de hacer cosas, nuestro potencial, la productividad y demás? Sí. ¿Hemos aumentado la complejidad de los recursos? Sí. ¿Hemos creado mucho más estrés a la hora del mantenimiento de estos recursos porque tenemos más dependencia de ellos? Sí. Esto genera mucha fricción y mucha incertidumbre porque hay cada vez más interacciones desde el principio hasta el final. + +Sí. ¿Cuál es el resultado de esto? Pues que nos encontramos frente a sistemas que llega a un punto, no sabemos cómo controlar, no tenemos mucha creencia de lo que puede o no funcionar y simplemente esperamos que funcione. Cuando no funciona, entonces lo arreglamos. + +Primera pregunta: ¿por qué tenemos que esperar a que no funcione o a que se rompa todo para arreglarlo? ¿Por qué no podemos prevenir antes que curar, fallar lo antes posible con el menor coste posible? +Segunda pregunta, ¿cómo hemos definido lo que queremos? Es decir, independientemente de cómo lo hagamos, ¿tenemos claro lo que queremos? ¿Tenemos claro cómo tiene que ser aquello que tiene que funcionar? ¿En qué términos? ¿Con qué restricciones? ¿Con qué condiciones? ¿Y bajo qué planteamientos? O sea, que hemos encontrado una fórmula para definir cuál es el contexto y los límites o las restricciones a ese contexto en el que queremos que se presten los servicios o bajo qué condiciones. +Tercera pregunta. Hemos encontrado o hemos creado herramientas que utilicen estas definiciones o estos límites o contextos y puedan trabajar de forma automática en los ciclos de vida de las aplicaciones y de los servicios para garantizar que se lleve a cabo lo que queremos, para que se haga en los términos en los que hemos, respetando los términos que hemos acordado previamente, En suma, ¿podemos tener una certeza de que al final tendremos aquello que hemos pedido o que hemos formulado, deseado o declarado? Con la seguridad o la tranquilidad de que eso va a ocurrir de una manera determinista, no de una manera aleatoria, con problemas que pueden ocurrir o no en mi máquina funciona, en aquella ya no lo sé. + +La comparación paralela que quiero hacer en cada una de las fases y en cada una de las preguntas y demás es la de cuando se va a un restaurante. Cuando se va al restaurante hay básicamente una cocina, un servicio que atiende al cliente y va y viene de la cocina a la mesa del cliente y un cliente sentado en una mesa, unos clientes sentados en una mesa que son los comensales. + +Lo que pasa entre cada una de estas tres partes tiene que ver con la gestión de procesos. Es totalmente declarativa porque el cliente no dice en la mesa cómo en cocina se tiene que elaborar cada uno de los platos que elegido. El cliente simplemente declara lo que quiere, el comensal declara lo que quiere y cuando se va al restaurante le pueden presentar una carta con A, B y C, o una carta en la que A puede tener variantes o sencillamente decirle esto es lo que hay hoy y el cliente elige. + +Entonces el servicio que atiende tiene que tener un conocimiento claro de lo que es posible y de lo que no y hacer una validación de esa petición para saber si es posible en tiempo o en forma en función de lo que se pedido. Y por otro lado tiene que entender las mecánicas de la cocina y la elaboración de platos. + +Cuando servirlos, cómo servirlos, cómo hacer esa conexión entre los clientes que están en mesa, el mundo del restaurante y el mundo de la cocina. Y con el menor coste, sacrificio y esfuerzo posible porque hay que hacer muchas cosas a la vez. + +Analicemos cada una de las cualidades que observamos En cada una de las diferentes etapas que hemos mencionado y en las fases de la petición de platos en un restaurante. En cada una de esas fases y procesos hemos elegido diferentes modos. ¿Qué es lo que tenemos que valorar? Primero, ¿hasta qué punto son eficientes y suficientes para expresar lo que queremos? + +No puede ocurrir que yo pida algo para que después el camarero vaya a cocina y el cocinero le diga No, de eso no hay, está fuera temporada, esto hoy no lo tenemos, entonces ¿por qué lo tienes en la carta? etc. No, yo he configurado un host con un puerto pero ese puerto no está permitido, hay que volver y reconfigurar el puerto y volver a empezar de cero. + +¿Es esta declaración suficiente y consistente, realista y rigurosa con lo que hay y con lo que es posible de acuerdo con las reglas? ¿Qué reglas? Esas reglas son dinámicas, son permanentes, son para toda la vida, el restaurante sólo ofrece A, B, C y D siempre a todas horas, no hay cambios. + +¿Cada cuánto tiempo se producen esos cambios? ¿Cuáles son los ciclos de vida? ¿De cambio que hay? ¿O todo es cambio continuo y adaptación? + +¿No hay un marco o unos límites estrictos? Entonces tenemos que valorar cómo de válida es esa declaración, hasta qué punto respeta los criterios y los límites y cuestionarnos, dar un paso atrás y cuestionarnos entonces cuál es la verdad. La verdad es la nota que toma el camarero o camarera sobre lo que uno quiere, lo que el cliente, el comenzar quiere, o las marcas que hace en esa nota en cocina sobre lo que ya está o lo que no está. + +O cómo a partir de eso se gestiona el ticket de pago. Hay una verdad que va mutando en cada proceso, no es estática. La verdad depende de quién lo mire y quién la use en cada momento. Hay cosas que son relevantes y cosas que no lo son. + +Como en un sistema neuronal hay neuronas que le prestan atención a unas cosas y a otras no. Son eventos que no, aunque ven, no consideran que son relevantes o que tienen que participar en ellos. + +Estamos hablando de validar un pedido o de validar una infraestructura, como se hacía antes, si hemos encontrado algún tipo de estándar, si ese estándar tiene una capacidad suficientemente adaptativa en los diferentes fases de todo el proceso, están definidas claramente las fases o las fases pueden cambiar dependiendo de lo que ocurra en los diferentes trayectos o procesos. + +Y más aún, finalmente hemos logrado aprender algo de toda esta dinámica para poder evitar errores en un momento dado y automatizar y facilitar todos los procesos, trayectos o viajes desde la entrada hasta la salida. ¿Cómo medimos esto? ¿Y qué valor tiene? ¿Qué peso tiene? ¿Qué repercusión tiene un fallo en un momento diferente, en un paso, en una situación diferente? No es lo mismo un fallo en la mesa que en la cocina, que la caída en un plato cuando se va a entregar la comida, que un desacuerdo entre lo que se pedido y lo que recibe en mesa cuando se entrega el plato, que una equivocación porque se cedido un tiempo de cocina en una elaboración o porque esa elaboración no contemplado diferentes elementos que se han asumido como parte de la historia. + +Hasta qué punto hemos logrado customizar los procesos. Es decir, si yo hago siempre el mismo tipo de tareas o si el cliente es el que ya conozco, asumo cosas que tengo que registrar. Porque puede haber elementos o procesos en la cadena que no conozcan al cliente. El camarero sí lo conoce, pero en cocina no. + +Entonces, para que haya este rigor, tengo que encontrar métodos que me aseguren que la fórmula es comprensible o que lo que se quiere es válido para cualquiera de los elementos que participan en la cadena. Y si alguno de estos elementos, llamémosles actores, o elementos que interactúen o estaciones del recorrido, pueda cambiar parte de lo definido y que eso tenga que volver a hacerse una validación al cliente. + +Es que no tengo champiñón. Puedo reemplazártelo por una verdura. + +Cuando empezamos con el HTML era perfecto porque era una manera de encapsular o una manera de guardar, reflejar el contenido para poderlo propagar de manera que se pudiera renderizar en la misma manera en diferentes puntos. Y pensamos que teníamos que usar más lenguajes y que para combinar esto pues ya no íbamos a escribir más HTML porque era un rollo. + +Y entonces usamos herramientas que escribían HTML, escribían CSS, Javascript y aparte interactuaban con los servicios de backend. ¿Qué ocurrió después? Después pensamos que esto en infraestructura no era muy válido porque en mucha de la infraestructura pues lo que hacía era que dentro del paquete pues tenía todos los dependencias, todas las acciones, los PAD y tal, todo hardcoded. + +Entonces pensamos en hacer de esto una configuración externa que pudiéramos declarar y que los programas o los instaladores pudieran usarla como modo de referencia donde poníamos los recursos de manera externa. Entonces como esto funcionaba muy bien entre máquinas utilizando JSON, pues el JSON se nos quedó como muy mal porque a muchos temas de sintaxis entonces pensamos que la mejor manera era utilizar un Tomele o un Yamele y que luego esto al cargarlo con herramientas, en por ejemplo Serde, el crate Serde, y así pues sería validado. + +Y que entonces pues aceptaríamos elefante como animal de compañía o no. Y esto lo pondríamos a fuego dentro de la aplicación para que se ejecutara o no. +¿Y qué pasó? Que nos cansamos de escribir yameles y que era muy estresante mantener los espacios, mantener una cuestión de lo que es la sintaxis, etc. Y entonces utilizamos gestores de plantillas para que fueran capaces de escribir el yamele. Entonces lo que hacíamos es que le pasábamos una serie de variables, otra vez en yamele, domele o json, y era capaz de generar el yamele final, que era el que consumía la aplicación. + +¿Todo esto valida el contenido del yaml? No, en absoluto. Es como ahora con la IA un markdown. En vez de escribir un prompt le paso un markdown o utilizo un markdown de referencia. Está validado su contenido. Puede ser sí o no o puede ofrecer una validación determinada como haríamos en un testing. No. +Entonces, nos movemos en ciclos de cambios, ciclos cambiantes, C y CD, de forma continua, pero ¿qué es lo que queremos? ¿Cuáles son las reglas de juego? ¿Y cuáles las acciones y los cambios que puedo introducir en esas reglas sin salirme fuera de contexto? sin salirme fuera, sin irme demasiado lejos de lo que quiero. Es decir, cuando tengo que volver a la mesa para preguntarle al comensal si le puedo cambiar determinada cosa en el plato porque no, esos ingredientes no son frescos de temporada. En realidad no estamos inventando nada, todo ya existe, el problema es si lo estamos gestionando adecuadamente. + +¿Qué pasa cuando aparece RustS? Pues cuando aparece RustS introduce elementos que ya están o estaban en otros lenguajes, los reúne y aparece algo bastante diferente. En RustS hay mutabilidad, en RAS hay tipado estático, en RAS hay posibilidades de definir componentes o interfaces, como son los traits. Y entonces puedo validar implementaciones en función de los traits. + +Tengo un compilador que analiza todo el código y el uso que se hace de los valores y variables antes de construir el binario. + +¿Qué me ofrece RAS o cuál es la propuesta? En primer lugar, tengo que introducir un tipado, o sea, crear tipos para definir exactamente lo que quiero, para que mi declaración pueda interpretarse sin equívocos. Un número no es lo mismo que un texto o un string. RAS es enormemente exhaustivo en los diferentes tipos y tipos naturales del lenguaje o tipos primarios que ofrece. + +En segundo lugar, es cohesivo y coherente con estos tipos, es decir, los valida de manera consistente, el tipado es estricto, no se relaja. Hay formas de pasar de un tipo a otro, de hacer las conversiones o los CAST. Hay métodos para gestionar la inmutabilidad, es decir, tengo que definir aquello que puede cambiarse o no de mi declaración. + +Yo he dicho que quiero tomate y puedas inventar lo que quieras, pero si no tiene tomate no es el plato que he pedido, ¿vale? Entonces, estos condicionantes de por sí aparecen como invariantes, no pueden cambiar. Pero además tengo la opción de que haya opcionales, no nulos, no inventate lo que quieras si no tienes esto, no. + +Hay algo que puedes ponerlo o no, ¿vale? Pero cuando no lo pones, puedo controlar que no está y cobrártelo o no cobrártelo. + +En el análisis de los valores, para cargarlos en una implementación validando un esquema de tipos, yo puedo establecer restricciones, es decir, que un número sea un rango entre n y n, o que un valor sea dentro de una serie de valores determinados, como es el caso del enum. + +Otro de los elementos importantes de Rust es la capacidad de definir funciones de llamada, sino analizar que esas funciones asociado determinado tipo de tipos y determinado tipo de implementaciones.Todos asociados a esa función. +Y luego tengo la posibilidad de definir traits, que ofrecen un sistema de composición, es decir, yo puedo componer implementaciones de un trait, variantes de un objeto en programación orientada a objetos, ¿vale? Que cumplen determinados tipos de criterios, normas o valores. + +Es decir, hay una serie de formas de declarar lo que quiero, de instrumentos para declarar lo que uno quiere, ¿vale? Que son vigilados y mantenidos para que haya una coherencia entre lo que se quiere formular. ¿Quién es el vigilante o el que supervisa todo esto? El compilador. Pregunta, ¿cuándo lo hace? + +Antes de construir el ejecutable. No cuando se está ejecutando. No cuando han pasado no sé cuánto tiempo de ejecución. No cuando toca llamar a una función a la que hace tiempo que no se llama. Es decir, el compilador intenta crear binarios o ejecutables predecibles en cómo se van a comportar con la memoria, con los recursos, con los valores y en los flujos de trabajo dentro de la propia aplicación. diff --git a/assets/presentation/2026-02-27-notas_voz_rust.md b/assets/presentation/2026-02-27-notas_voz_rust.md new file mode 100644 index 0000000..441c5a0 --- /dev/null +++ b/assets/presentation/2026-02-27-notas_voz_rust.md @@ -0,0 +1,39 @@ +Ahora vamos a explicar cómo resolvemos esto desde Rust sin mencionar nickel, pero usando sus conceptos, que luego decidiremos que es la opción para manejar los tipados de forma externa y por qué, pero esto lo explicamos más tarde. + +En primer lugar necesitamos un esquema, es decir, necesitamos tener un patrón que determine que sirva de contenedor para lo que queremos declarar. En este caso, una forma de escribir, tener las opciones del menú para que se puedan elegir o no, unas estructuras en RASP, un esquema donde definimos que la cosa tiene, está asociada, cada elemento, cada ítem está asociado a un tipo, a un valor, un número, un string, un enum, etc. + +Junto a este elemento podemos asociarle unos valores por defecto, ¿vale? De manera que cuando no se proporciona un valor, utilice un valor por defecto. El tercer elemento es un constraint, una limitación dentro del tipo. Por ejemplo, en un puerto puedo usar un rango específico. + +En un plato puedo determinar tiene que ser un tipo de verdura de temporada, no solamente vegetal. + +Ahora introducimos dos elementos más. El primero es un validador, es decir, en mi declaración puedo introducir un validador, ¿vale? Es decir, dentro del rango de precios, dentro de lo que es posible en cocina, utilizo una función que valida que lo que estás introduciendo es un número entre un valor u otro, que ese número se corresponde con otro valor o con otro ítem dentro de la misma declaración. + +Los validadores los ponemos todos juntos y los podemos aplicar a diferentes declaraciones, estructuras o esquemas, al final son funciones con una entra y una salida. + +El segundo elemento es la composición, es decir, yo puedo sobreescribir o cambiar determinado conjunto o implementación de valores por defecto, puedo utilizar determinadas definiciones generales o globales, heredarlas y cambiarlas. +Algo que podemos usar en Rust con funciones públicas o privadas y usando Traits. + +¿Cuándo se valida todo esto? ¿Quién lo valida y con qué? Evidentemente no cuando el pedido ya llegado a cocina, sino antes de salir de la mesa. Incluso ya el comensal sabe lo que puede y lo que no puede hacer. Están las reglas preestablecidas. Falla en el tiempo de compilación, en el tiempo de construcción de la comanda o de la petición. +Falla en el momento en el que se articula lo que se quiere. + +Esto es lo que elaboré en mi primera herramienta de despliegue en Rust CloudMandala en 2021. +Usando el crate de Serde y parsing de valores cargados vía configfiles o environment values. +¿ Es correcto, es suficiente, es eficiente ? Sí, pero no. Sí en sí mismo, pero no, porque los mecanismos de despliegue cada vez se requiere que sean más ágiles, con iteración más rápida, los ciclos son cada vez más frecuentes. Si lo hago en RUST, en el momento en el que cambio una definición, un tipo, una declaración, una limitación, constraint o restricciones de valor, tengo que volver a recompilar el target de RUST y tengo que distribuir este target a la arquitectura que corresponda y en el lugar en el que tiene que estar para cumplir esta validación y seguir con los procesos. + +Es decir, esto me somete a que los ciclos de cambio y de adaptación son mucho más largos de lo que en sí demandan la CD/CI actual. En definitiva, es eficiente, pero no es suficientemente ágil. Entonces, ¿cómo puedo mantener estas funcionalidades sin Rust o de otra manera? Y para eso pensamos en usar lo que llamamos REPL, que son Read Evaluate Print Loop, es decir, programas que se ejecutan y que realizan determinado tipo de tareas de manera iterativa, en principio use Dhall (que está en Haskel) al final he utilizado KCL y finalmente Nickel que están escritos en Rust. Todos ellos permiten utilizar imports incluso desde repos git de bibliotecas de globales, de esquemas, utilizar defaults, utilizar constraints de valores mediante funciones con programación funcional, y además permiten composición y merge de valores. Su Su responsabilidad no va más allá de producir algo consistente y exportar en diferentes formatos de consumo, como JSON, Yaml o Toml. Esto significa que los targets que cargan estos ficheros, incluso tengo la carga directa desde ficheros NCL, que son los ficheros del tipo nickel, no tengan que hacer una segunda validación o una segunda consideración de los valores definidos en las declaraciones de forma exhaustiva. Estos REPL usan ficheros editables que puedo cambiar sobre la marcha de manera AGIL. + +¿Es esto suficiente? No. ¿Por qué? Porque tener la declaración no significa que el camarero haya llevado la comanda a la cocina y que haya traído los platos a la mesa. Para eso necesito procedimientos, controles, ejecuciones, una serie de gestión de todos estos recorridos y tareas. ¿Lo puedo hacer esto en Rust? + +Sí, puedes hacer llamadas al sistema, puedes hacer controles, etc. Pero esto también tiene que ser ágil, es decir, tengo que poder cambiar los métodos y procedimientos con agilidad para reaccionar en el caso de que se caiga un plato por el camino, en el caso de que ocurra cualquier contingencia que cambie el plan de acción. Y para resolver esto, en vez de Rust, volvemos a usar scripts y volvemos a usar templates con engines tipo el crate de Tera. Nushel es una shell escrita en Rust, otro REPL que podemos usar pegamento o seguimiento o definir interacciones sobre la marcha, de una manera mucho más eficiente que un script normal, porque sigue los patrones de Rust de forma bastante intensiva. +Es más, falla si no es capaz de cargar el código al inicio. Nushell tiene bucles "for par" para ejecutar acciones o llamadas en paralelo, por ejemplo, llamar a la CLI un proveedor para crear varias instancias a la vez en paralelo. + +Entonces, ¿para qué necesito RAS? Para nada. Entonces, ¿lo resuelvo todo con REPLs? No. Aquí es donde vuelvo de nuevo a RAS. ¿Por qué? Porque RAS ofrece una serie de capacidades de trabajo con multi-threads seguras que no están al alcance de la mayor parte de los lenguajes, scripts y demás que pueda utilizar en infraestructura. + +Eso hace que todas estas acciones con proveedores, ejecución de scripts que se han generado combinando valores declarados con plantillas, se realicen a través de un target en Rust que funciona como un orquestador. Él es capaz de ejecutar en paralelo, guardar valores seguros, recoger los retornos de resultado en las ejecuciones, realizar notificaciones de éxito o fracaso, reconducir o relanzar scripts que se predefinen para tareas alternativas e incluso rollback a estados previos, actua como un control-plane con gestion de estados y notificaciones. +Es capaz de conectarse con vaults para obtener de forma los credenciales de acceso vía API a un proveedor, etc. + +Es suficiente para tener una maquinaria ¿Qué es la infraestructura? Es que la infraestructura no solamente son instancias, servidores, redes y demás, son también servicios que se declaran dentro de esa infraestructura, son configuraciones de estos servicios. Queremos que todo esté declarado y que sea elegible. No es primero este para crear instancias y luego aqué en Python con scripts shells para controlar la instalación de los paquetes, instalar la configuración de los servicios dentro de cada uno, bla, bla, bla. + +Como casi todo requiere de Infraestructura, de componentes que sean SOLID que puedan iteractuar con una declaración que se va adaptando a lo que va sucediendo ... Pero entonces, ¿dónde está la verdad? Mejor preguntémonos previamente qué es lo que entendemos por verdad. Si es algo inmutable, una especie de ley que fue declarada, escrita en el libro sagrado, ¿cómo vamos a darle credibilidad a esto? Y cómo se va a ir adaptando a lo que vamos necesitando en cambios sucesivos, ágiles, pero que mantienen una coherencia y una credibilidad a pesar de una dinámica continua. Rust no es muy puro ¿ tal vez porque admite y contempla modos de mutar a pesar de empezar con un enfoque inmutable ? (Aquí svg animado de los recursos de Rust para mutar y gestionar la inmutabilidad). + +Aquí mostramos la cosmología/arquitectura de Provisioning https://provisioning.systems/architecture-diagram.html diff --git a/assets/presentation/QUICKSTART.md b/assets/presentation/QUICKSTART.md new file mode 100644 index 0000000..d680c7e --- /dev/null +++ b/assets/presentation/QUICKSTART.md @@ -0,0 +1,109 @@ +# Quick Start + +## Installation & Run + +```bash +cd /Users/Akasha/personal/0-Jobs/rustikon-2026-slides +npm install +npm run dev +``` + +Then open http://localhost:3030 + +## Edit Slides + +Edit `slides.md` in your editor. Changes hot-reload automatically. + +## Key Commands During Presentation + +- `f` - Fullscreen +- `p` - Presenter view with notes +- `g` - Go to slide number +- `o` - Overview mode +- `ESC` - Exit presentation + +## Export + +```bash +# Export as PDF +npm run export:pdf + +# Export as PNG (one per slide) +npm run export:png + +# Build static HTML +npm run build +``` + +## Theme + +Custom dark theme with Rust orange is in `theme/dark-rust.css` + +Modify colors: +- Rust orange: `#CE422B` +- Background: `#0F0F0F` +- Text: `#E8E8E8` + +## Add Images + +1. Place images in `public/` directory +2. Reference in slides.md: `![Alt](/image.png)` + +## Slide Syntax + +See [Slidev docs](https://sli.dev/guide/syntax.html) for full syntax. + +Quick examples: + +```markdown +--- +layout: cover +--- +# Title Slide + +--- +layout: two-cols +--- + +# Left +Content + +::right:: +# Right +Content + +--- +layout: section +--- +# Section Title + +--- + +Regular slide with **bold**, *italic*, `code` + +- Bullet points +- With nesting + - Level 2 +``` + +## Speaker Notes + +Notes at the bottom of each slide: + +```markdown +--- +layout: default +--- + +# Slide Title + +Slide content + + +``` + +Press `p` during presentation to see presenter view with notes. + +Done! Happy presenting. 🦀 diff --git a/assets/presentation/README.md b/assets/presentation/README.md new file mode 100644 index 0000000..0dc7949 --- /dev/null +++ b/assets/presentation/README.md @@ -0,0 +1,114 @@ +# Rustikon 2026 Presentation + +**Why I Needed Rust, Finally: Infrastructure Automation I Can Sleep On** + +Presentation slides built with [Slidev](https://sli.dev/) + +## Setup + +### Prerequisites +- Node.js 16+ (or Deno/Bun) +- npm or pnpm + +### Installation + +```bash +cd rustikon-2026-slides +npm install +``` + +### Development + +Run the development server with hot reload: + +```bash +npm run dev +``` + +Open `http://localhost:3030` in your browser. + +### Build + +Build slides for distribution: + +```bash +npm run build +``` + +Output will be in `dist/` + +### Export + +Export as PDF: + +```bash +npm run export:pdf +``` + +Export as PNG (per slide): + +```bash +npm run export:png +``` + +## Structure + +``` +. +├── slides.md # All slides content +├── slidev.config.ts # Slidev configuration +├── theme/ +│ └── dark-rust.css # Custom dark theme with Rust branding +├── public/ # Static assets (images, etc.) +└── package.json +``` + +## Customization + +### Theme + +The presentation uses a custom dark theme with Rust orange accents (`#CE422B`). + +Edit `theme/dark-rust.css` to customize colors, fonts, or styles. + +### Content + +All slide content is in `slides.md`. Follow [Slidev markdown syntax](https://sli.dev/guide/syntax.html). + +### Images + +Place images in `public/` directory and reference them in slides.md: + +```markdown +![Alt text](/image-name.png) +``` + +## Presentation Notes + +Speaker notes are included at the end of `slides.md` under each slide's frontmatter or in the notes section. + +Press `p` during presentation to see presenter view with notes. + +## Keyboard Shortcuts + +- `f` — Fullscreen +- `p` — Presenter view +- `g` — Go to slide +- `o` — Overview +- `j/k` — Next/previous slide +- `ESC` — Exit presentation + +## Deployment + +To host on GitHub Pages: + +1. Build the slides: `npm run build` +2. Push `dist/` to your GitHub Pages branch + +Or use Slidev's built-in deployment options. + +## Author + +Jesús Pérez Lorenzo + +Rustikon 2026 diff --git a/assets/presentation/TO_CHANGE.md b/assets/presentation/TO_CHANGE.md new file mode 100644 index 0000000..6c4cabb --- /dev/null +++ b/assets/presentation/TO_CHANGE.md @@ -0,0 +1,86 @@ +## Nickel y Nushell — sí, pero con cuidado + +Primero el diagnóstico honesto: + +``` +Rust → la audiencia es Rust — terreno seguro +Nickel → muy pocos en la sala lo conocerán +Nushell → algunos lo conocen, mayoría no +``` + +El riesgo es que suenen como stack personal en lugar de solución general. La oportunidad es exactamente esa rareza — **tú los usas en producción cuando casi nadie lo hace todavía**. + +--- + +## Lo que cambiaría en el slide de Nickel + +Ahora dice: + +> *"YAML rejected. TOML rejected. Reason: no type safety."* + +Eso es defensivo. Empieza por la solución, no por el rechazo: + +``` +Antes: "YAML rejected. TOML rejected." + (suena a preferencia personal) + +Después: "Config is code. If it's code, + it deserves a type system." + (suena a principio) +``` + +Y añadir una línea que conecte con el DAG: + +> *"Nickel is where the DAG is defined. Rust is where it runs. The compiler validates both."* + +--- + +## Nushell — actualmente casi invisible + +La charla lo menciona en los PDFs de tu proyecto pero no aparece como concepto en los slides. Eso es una pérdida porque Nushell es **el puente** — el lugar donde el DAG definido en Nickel se convierte en ejecución Rust. Sin ese puente, la historia tiene un agujero. + +Un slide corto o una adición al slide de Nickel: + +``` +Nickel → defines what is valid (compile time) +Nushell → orchestrates what happens (the bridge) +Rust → executes with guarantees (runtime) + +Three layers. Each catches what the previous can't. +``` + +Eso no necesita explicar qué es Nushell en detalle. Solo su rol en la cadena. + +--- + +## La optimización más importante de todas + +Ninguna de estas. Es esta: + +**El slide de los metros — 🛡 😴 🔥 — aparece por primera vez en Stage 1 pero nunca se explica.** + +Si lo explicas en 10 segundos al inicio: + +> *"Three metrics. Security. Sleep. Fire-fighting. Watch them move."* + +Y luego dejas que evolucionen solos a lo largo de la charla, tienes un hilo visual que hace todo el trabajo emocional sin palabras. El momento donde 🔥 baja de ●●●●● a ●○○○○ después de Rust es el momento más poderoso de la charla — pero solo si la audiencia sabe desde el principio lo que están viendo. + +--- + +## Prioridad de cambios + +``` +Impacto alto, esfuerzo mínimo: + 1. Explicar los metros al inicio — 10 segundos + 2. DAG — tres menciones quirúrgicas ya descritas + 3. Nickel: cambiar "rejected" por el principio + +Impacto medio, algo de trabajo: + 4. Nushell visible como puente — añadir al slide de Nickel + 5. "More years" con la segunda mitad + +Dejar como está: + 6. Todo lo demás — no tocar lo que funciona +``` + +> El mayor riesgo de una charla buena no es mejorarla — es sobre-editarla. Estos cinco cambios tienen retorno claro. Más allá de aquí, el rendimiento decreciente aplica. diff --git a/assets/presentation/_slides.md b/assets/presentation/_slides.md new file mode 100644 index 0000000..b71d010 --- /dev/null +++ b/assets/presentation/_slides.md @@ -0,0 +1,117 @@ +--- +theme: default +#theme: ./themes/rust-vibe +title: Why I Needed Rust +titleTemplate: '%s - StratumIOps' +layout: cover +keywords: Rust,programming +download: true +exportFilename: StratumIOps Ontology and Reflection +monaco: true +remoteAssets: true +selectable: true +record: true +colorSchema: dark +#colorSchema: white +lineNumbers: false +themeConfig: + primary: '#f74c00' + logoHeader: '/ferris.svg' +fonts: + mono: 'Victor Mono' +drawings: + enabled: true + persist: false + presenterOnly: false + syncAll: true +scripts: + - setup/image-overlay.ts +#background: ./images/charles-assuncao-1BbOtIqx21I-unsplash.jpg +class: 'justify-center flex flex-cols photo-bg' +--- + +

Ontology and Reflection

+ +

+ StratumIOps +

+ +
+ +
+ + + +