Commit Graph
11 Commits
Author SHA1 Message Date
sergioandClaude Opus 4.8 b979d9e550 Etapa G: importador nix→receta (hammer import-nix + scripts/nix-import.sh)
Poblar el catálogo no es opcional: 34 recetas a mano = userland desierto. nixpkgs es el mayor set
de recetas DESDE FUENTE ⇒ semilla natural. Importamos la RECETA (source+hash+deps), nunca el
binario del cache de nix — hammer reconstruye desde fuente ("verificar, no confiar").

- crates/hammer-cli/nix_import.rs: consume el JSON normalizado de nix y emite una receta hammer.
  Clasifica el origen: fetchurl flat → tarball+sha256 (convierte el hash nix SRI/base32/hex → hex);
  fetchFromGitHub → repo+commit (hammer pinea por commit, no necesita el hash NAR). Filtra el ruido
  de stdenv (setup-hooks, wrappers). nix_base32 decode portado. 11 tests.
- `hammer import-nix [FILE|-]` (stdin) → receta .toml; valida que parsee como Recipe.
- scripts/nix-import.sh <attr>: `nix eval --apply` produce el JSON normalizado y lo pipea al
  importador. NIX_STORE= para store local si /nix/store no es escribible.
- VALIDADO contra nixpkgs REAL (nix 2.34): import hello (tarball, sha256→hex) + ripgrep (github→
  repo+commit); pipeline completo nix→import→pack→.swm probado con hello. 31 suites verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 07:41:50 -04:00
sergioandClaude Opus 4.8 f94ef01139 Etapa F refinamientos: uninstall poda dirs vacíos + install --require-signed
Dos bordes ásperos de la paquetería:
- uninstall ahora retira los directorios que quedaron VACÍOS por el borrado (rmdir de abajo
  arriba; remove_dir sólo borra dirs vacíos ⇒ se detiene solo al toparse con contenido de otro
  paquete). Antes dejaba /usr/bin, etc. huérfanos.
- `install --require-signed`: modo estricto que ABORTA si el release no está firmado por una
  clave confiada (Unsigned o UnknownKey ⇒ error). No basta con que el .swm reproduzca: exige
  autoría verificada del catálogo. Default off (no rompe flujos sin firma).

Validado E2E: uninstall bwrap poda 4 dirs; --require-signed aborta sin firma y procede con
release trusted. 31 suites verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 07:12:58 -04:00
sergioandClaude Opus 4.8 910db267ef Etapa F paquetería #6: repo sobre red — install --repo URL (HTTP/HTTPS)
El repo son ficheros estáticos (index.json + .swm) ⇒ cualquier servidor estático lo sirve.

- hammer-cli: `RepoSource` {Local(path) | Http(url)}. `install --repo` ahora acepta path o URL.
  Para HTTP: lee index.json por GET, materializa un repo LOCAL temporal bajando el índice + los
  .swm del cierre de deps (curl, vía download::fetch_url_bytes), y de ahí el flujo es IDÉNTICO al
  local (resolución de deps, verificación de release/firma/base, reproduce + hidrata). tempfile
  pasa a dep normal de hammer-cli.
- Validado E2E: server HTTP estático + install openssh vía http:// → "release: trusted" (índice
  firmado bajado por red) → "repo: bajados 3 .swm" (cierre openssh+zlib+openssl) → resuelve del
  temporal. (El proxy del sandbox exige NO_PROXY para localhost; el código es correcto.) 31 verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 07:07:32 -04:00
sergioandClaude Opus 4.8 9d1b2d4a76 Etapa F paquetería #5: DB de instalados + hammer uninstall/installed
Funcionalidad de gestor de paquetes: rastrear qué hay instalado y poder quitarlo.

- hammer-core/installed.rs: `InstalledDb` (name→{version,hash,files}) load/save JSON; record
  (upsert), remove, `owned_by_others` (refcount por ruta). Rutas absolutas ⇒ uninstall no
  necesita el root. 4 tests.
- hammer-cli: run_apply ahora DEVUELVE los ficheros que CREA (hidratados + file_drop + init_rule;
  config_edit modifica, no crea ⇒ no se registra ni se deshace). install los registra en la DB
  (--db, default /var/lib/hammer/installed.json). `uninstall <nombre>` borra esos ficheros salvo
  los que otro paquete instalado aporta (refcount) y quita la entrada. `installed` lista.
- Validado E2E REAL: install bwrap (con dep libcap) → registra 2 ficheros → `installed` los
  lista → `uninstall bwrap` los borra (prefix vacío, DB vacía). 31 suites verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 07:01:46 -04:00
sergioandClaude Opus 4.8 977f07ca6c Etapa F paquetería #4: firma del release (índice del repo firmado)
Cierra el hueco de seguridad: hoy se firmaba cada .swm (autoría del paquete) pero NO el
catálogo ⇒ un atacante podía añadir/quitar/intercambiar entradas del index.json. Firmar el
release ancla qué paquetes/versiones/hashes existen.

- hammer-core/sign.rs: extraídos `KeyPair::sign_raw` + `verify_raw` genéricos (bytes canónicos
  arbitrarios); Swm::{sign,verify_signature} ahora los reusan (DRY, sin cambio de comportamiento).
- hammer-core/repo.rs: `RepoIndex.signature` (Ed25519 sobre la lista de paquetes canónica,
  excluye la propia firma) + `sign`/`verify_signature`. `upsert` INVALIDA la firma (cualquier
  cambio al catálogo ⇒ re-firmar). 4 tests (sign→verify, survive save/load, upsert-invalida,
  tamper→BadSig).
- hammer-cli: `repo sign --key` / `repo verify`; `install` VERIFICA el release antes de resolver
  (BadSig ⇒ aborta "el índice fue manipulado"); `repo list` muestra si está firmado.
- Validado E2E host: sin firmar→firmar→trusted→install lo ve; MANIPULAR el índice sin re-firmar
  ⇒ install ABORTA; re-publicar invalida la firma. 31 suites verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:53:31 -04:00
sergioandClaude Opus 4.8 b89e2dba51 Etapa F paquetería #3: deps entre paquetes — install resuelve el cierre y reconstruye el catálogo
Cierra el hueco que pack/install advertían: un paquete con build-deps (bwrap→libcap,
openssh→zlib,openssl) ahora se instala por nombre reproduciéndose desde fuente CON sus deps.

Modelo: las build-deps viajan por NOMBRE en el source_patch y en la PackageEntry; install
resuelve el cierre transitivo desde el índice y reconstruye un catálogo de recetas que el lab
consulta al materializar deps en el sandbox.

- hammer-core: `Mutation::SourcePatch.deps` (Deps, serde-skip si vacío) + `from_recipe` lo
  carga. `Deps::is_empty`. `RepoIndex`/`PackageEntry.deps` + `resolve_closure(name)` (DFS
  topológico, deps antes que dependientes, detecta dep faltante y ciclo). 8 tests nuevos.
- hammer-build/swm_bridge: refactor — `recipe_from_source_patch` (síntesis pública, setea deps
  + base_dir=catálogo) + `catalog_dir_for` (dir determinista compartido). build_source_patch
  lo reusa. synthesize_recipe ahora setea recipe.deps + base_dir al catálogo (no "/").
- hammer-cli: pack puebla PackageEntry.deps; install resuelve el cierre y escribe un {dep}.toml
  por dep en el catalog_dir ANTES de aplicar el target (mismo dir determinista que usa
  build_source_patch ⇒ el lab resuelve {dep}.toml por nombre). Warning de pack actualizado.
- VALIDADO E2E REAL contra ./store: `install bwrap` resuelve libcap del catálogo y reproduce
  el artefacto CACHEADO EXACTO (b3:f89e716…) → hidrata bwrap (1.8MB ELF). El paquete con dep
  hashea bit-idéntico al original. Resolución/diamante/faltante/ciclo unit-tested. 31 suites verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:43:30 -04:00
sergioandClaude Opus 4.8 e867bac388 Etapa F paquetería #2: repositorio + hammer install <nombre> / repo list
Cierra el lazo "packié un .swm → lo instalo por nombre". El repo es el namespace que
le da identidad a los .swm (que en sí no la llevan).

- hammer-core/repo.rs: `RepoIndex` + `PackageEntry` (load/save index.json, find, upsert
  idempotente por nombre que reporta el .swm huérfano). Índice JSON plano, ordenado,
  diffeable, firmable a futuro como release. 4 tests.
- hammer-cli:
  * `pack --repo DIR` PUBLICA (escribe <repo>/<name>-<version>.swm + upsert al índice con
    distro_version/expected_hash/signed_by; retira el huérfano de una versión vieja).
  * `install <nombre> [--repo] [--trust] [--base-ref] [--prefix] [--skip-source-patch]`
    CONSUME: resuelve nombre→.swm, verifica firma (con --trust) ANTES de reproducir, delega
    en el camino de apply (reproduce source_patch + hidrata). Nunca corre binario ajeno.
    Nombre inexistente → error legible con los disponibles.
  * `repo list` imprime el catálogo.
- Validado E2E en host: publicar ripgrep (firmado) + findutils, repo list, index.json limpio,
  install ripgrep --trust → "firma: trusted (by alice)" → apply OK. 31 suites verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:28:16 -04:00
sergioandClaude Opus 4.8 339a7b07ef Etapa F paquetería #1: hammer pack — receta del corpus → paquete .swm (source_patch)
Cierra la dirección forward que faltaba (SDD 06 §6 la marcaba "para más adelante"):
una receta que el sistema ya sabe construir se vuelve un paquete distribuible y
reproducible-desde-fuente, inversa de `hammer apply`.

- hammer-core: `Swm::from_recipe(recipe, target_bin, patch_text, expected, distro)`
  (constructor puro: el caller lee los patches). `SwmBuild` gana `phases`+`zig_version`
  y `SourcePatch` gana `strip_components` (Option/skip ⇒ .swm viejos parsean igual) para
  reproducir con fidelidad el corpus real (22/34 recetas usan phases, 8 usan zig 0.13).
- hammer-build/swm_bridge: la dirección inversa (source_patch→Recipe→build) ahora traslada
  phases/zig_version/strip_components a la receta efímera ⇒ apply rehace idéntico.
- hammer-cli: `hammer pack <recipe> [--target-bin] [--out] [--expected|--build] [--sign]`.
  Concatena los patches inline; avisa si la receta declara deps (el source_patch aún no
  las modela = pieza posterior). `export` también enriquece su source_patch.
- Validado en host: ripgrep (git+patch+install custom), openssl (tarball+zig 0.13+phases),
  coreutils (multicall), findutils firmado → swm-verify "trusted". Tests core+bridge verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 06:20:44 -04:00
SergioandClaude Opus 4.8 8a5e75344a feat(swm): init_rule se materializa a /etc/hammer/init.d/{service}.rule
Deja de ser un no-op "pendiente fase 5": apply::apply_init_rule escribe una
regla TOML por servicio (service/action/command); enable/start/restart la
escriben, disable/stop la retiran (idempotente), con guardas de nombre y acción.
CLI y orchestrator la aplican (rebaseando con prefix/overlay). Es el contrato
on-disk que el init (arje) lee para supervisar. 3 tests + doc del formato.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 03:17:56 +00:00
Sergio 09d50f9e60 Fase 4 — formato .swm: verify, apply (config_edit/file_drop/source_patch) y export
- hammer-core::swm: verify_schema (invariantes por mutación) + verify_base con
  BaseRef/BaseCompat/PinDiff (distro_version + pins). FileDrop.content_b64 para
  .swm autocontenidos.
- hammer-core::apply: primitivas puras apply_config_edit (hunks -/+ con búsqueda
  exacta de bloque, ambiguo => error), apply_file_drop (base64 + verify BLAKE3),
  rebase_path para tests con prefix.
- hammer-build::swm_bridge: Mutation::SourcePatch -> Recipe sintética + patch
  inline materializado + build, con comparación opcional contra expected_hash.
- hammer-cli: nuevos subcomandos apply [--prefix --base-ref --skip-source-patch
  --state-root], swm-verify, export --journal --base-ref --since.
- Tests: 18 unit nuevos en hammer-core (apply + verify), 5 en swm_bridge,
  4 e2e en hammer-cli (roundtrip yaml -> apply bajo prefix reproduce los archivos).
- Roadmap y SDD 06 actualizados con lo cerrado y lo pendiente (URLs remotos,
  provenance en export vía mapa artefacto->receta, firma ed25519).
2026-06-09 15:19:33 +00:00
sergioandClaude Opus 4.8 8bf1623044 Scaffold inicial: workspace Rust + SDDs completos
Arranque del proyecto hammer (distro AI-nativa: laboratorio funcional en el
sótano, terminal mutable clásica arriba, integración de IA programadora).

- Workspace Rust (compila, tests verdes): hammer-core, hammer-build,
  hammer-cli (bin `hammer`), hammerd.
- SDDs 00-10 + 6 ADRs en docs/ con toda la arquitectura.
- Esqueletos navegables mapeados a las fases del roadmap; Fase 0/1 listas
  para implementar el sandbox real.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 19:06:04 +00:00