H4b: gate de compatibilidad de configs en la receta real + hammer install (SDD 15 §H4)
Sube el modelo de slots de wawa-memo (prototipo host) a hammer:
- Recipe + source_patch del .swm llevan bloque `slots` {claims, requires} (slot->b3:…),
FUERA de hash_inputs (topología ≠ identidad, no mueve el artifact_hash). Viaja intacto
por los dos sentidos del puente (Recipe->.swm->Recipe): test de round-trip.
- InstalledDb registra claims por paquete + system_state() -> slot->hash (el Estado del gate).
- hammer-core::compat::evaluar(estado, slots) -> Veredicto {Compatible, Colision, Incompatible}
(el álgebra probada en wawa-memo, sobre tipos de hammer).
- Gate en `hammer install`: antes de tocar nada evalúa el paquete contra el estado instalado.
Incompatible (requisito sin resolver, caso wayland) -> aborta; Colisión (caso logo) ->
aborta pidiendo elección salvo --force-slots; Compatible -> procede y registra los claims.
Plumbing propagado por los 5 sitios de Mutation::SourcePatch (from_recipe, swm_bridge,
export, bus, orchestrator). Tests: compat (4) + slots-no-en-hash/round-trip (2) +
system_state (1) + puente receta<->swm (1). Workspace compila y verde.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -241,6 +241,11 @@ enum Cmd {
|
||||
/// (no basta con que el `.swm` reproduzca — exige autoría del catálogo verificada).
|
||||
#[arg(long)]
|
||||
require_signed: bool,
|
||||
/// [H4b] Ante una COLISIÓN de superficie (el paquete reclama un slot que ya ocupa otra
|
||||
/// config, p. ej. el logo), adopta la nueva en vez de abortar. No afecta a un requisito
|
||||
/// insatisfecho (ese rechazo es duro: el paquete no encaja con lo que tenés).
|
||||
#[arg(long)]
|
||||
force_slots: bool,
|
||||
},
|
||||
/// [Etapa F] Desinstala un paquete: borra los ficheros que registró (refcount: respeta los
|
||||
/// que otro paquete instalado también aporta) y lo quita de la DB de instalados.
|
||||
@@ -852,6 +857,7 @@ fn main() -> anyhow::Result<()> {
|
||||
state_root,
|
||||
db,
|
||||
require_signed,
|
||||
force_slots,
|
||||
} => {
|
||||
run_install(
|
||||
&cli.store,
|
||||
@@ -864,6 +870,7 @@ fn main() -> anyhow::Result<()> {
|
||||
state_root.as_deref(),
|
||||
&db,
|
||||
require_signed,
|
||||
force_slots,
|
||||
)?;
|
||||
}
|
||||
Cmd::Uninstall { name, db } => run_uninstall(&name, &db)?,
|
||||
@@ -1590,6 +1597,7 @@ fn run_install(
|
||||
state_root: Option<&std::path::Path>,
|
||||
db_path: &std::path::Path,
|
||||
require_signed: bool,
|
||||
force_slots: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
let source = RepoSource::parse(repo);
|
||||
let index = source.read_index()?;
|
||||
@@ -1658,6 +1666,48 @@ fn run_install(
|
||||
eprintln!("firma: no verificada (pasá --trust <dir> para validar la autoría)");
|
||||
}
|
||||
|
||||
// Gate de compatibilidad (H4b — configs compatibles, SDD 15 §H4). Antes de reconstruir o tocar
|
||||
// nada, evaluamos las superficies que el paquete declara (`slots`) contra el estado INSTALADO.
|
||||
// Es el chequeo local y reproducible de "cuando instalo, ¿es compatible con lo que ya tengo?".
|
||||
let entrant_slots = source_patch_slots(&load_swm(swm_path_str)?);
|
||||
if !entrant_slots.is_empty() {
|
||||
entrant_slots
|
||||
.validate()
|
||||
.map_err(|e| anyhow::anyhow!("slots del paquete inválidos: {e}"))?;
|
||||
let idb = hammer_core::InstalledDb::load(db_path)?;
|
||||
let estado = idb.system_state();
|
||||
match hammer_core::compat::evaluar(&estado, &entrant_slots) {
|
||||
hammer_core::CompatVeredicto::Compatible => {
|
||||
eprintln!(
|
||||
"compat: compatible ({} reclamo(s), {} requisito(s) resuelto(s))",
|
||||
entrant_slots.claims.len(),
|
||||
entrant_slots.requires.len()
|
||||
);
|
||||
}
|
||||
// Caso wayland: una dependencia sobre una superficie que el usuario divergió. Rechazo duro.
|
||||
hammer_core::CompatVeredicto::Incompatible { slot, requerido, presente } => {
|
||||
anyhow::bail!(
|
||||
"compat: INCOMPATIBLE — '{name}' requiere la superficie '{slot}' @ {requerido}, \
|
||||
pero el sistema tiene {}. Divergiste esa superficie: no se instala sobre ella.",
|
||||
presente.as_deref().unwrap_or("(ausente)")
|
||||
);
|
||||
}
|
||||
// Caso logo: dos configs escriben la misma superficie. Es una ELECCIÓN, no un error.
|
||||
hammer_core::CompatVeredicto::Colision { slot, instalado, entrante } => {
|
||||
if force_slots {
|
||||
eprintln!(
|
||||
"compat: colisión en '{slot}' ({instalado} → {entrante}); --force-slots ⇒ me quedo con la nueva"
|
||||
);
|
||||
} else {
|
||||
anyhow::bail!(
|
||||
"compat: COLISIÓN en la superficie '{slot}' — ya la ocupa {instalado} y '{name}' \
|
||||
quiere {entrante}. Es una elección: pasá --force-slots para adoptar la nueva."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dependencias: resolvemos el cierre transitivo de build-deps desde el índice (deps antes
|
||||
// que el dependiente) y poblamos el catálogo de recetas que el lab consultará al reproducir
|
||||
// el `source_patch`. Sin esto, un paquete con build-deps (openssh→zlib,openssl) no podría
|
||||
@@ -1729,6 +1779,9 @@ fn run_install(
|
||||
version: pkg_version,
|
||||
hash: pkg_hash,
|
||||
files,
|
||||
// Las superficies reclamadas quedan en la DB ⇒ pasan a formar el `system_state` que el
|
||||
// gate de la PRÓXIMA instalación consultará (H4b). Así una colisión se detecta después.
|
||||
claims: entrant_slots.claims.clone(),
|
||||
});
|
||||
idb.save(db_path)?;
|
||||
eprintln!("registrado {pkg_name} ({nfiles} fichero(s)) en {}", db_path.display());
|
||||
@@ -1949,6 +2002,15 @@ fn source_patch_of(swm: &hammer_core::Swm) -> Option<&hammer_core::Mutation> {
|
||||
.find(|m| matches!(m, hammer_core::Mutation::SourcePatch { .. }))
|
||||
}
|
||||
|
||||
/// Las superficies del sistema (H4b) que declara el `source_patch` de un `.swm`, o `Slots` vacío
|
||||
/// si el paquete no declara topología. Lo consume el gate de compatibilidad de `install`.
|
||||
fn source_patch_slots(swm: &hammer_core::Swm) -> hammer_core::Slots {
|
||||
match source_patch_of(swm) {
|
||||
Some(hammer_core::Mutation::SourcePatch { slots, .. }) => slots.clone(),
|
||||
_ => hammer_core::Slots::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Firma el índice de un repo (release). Re-firmá tras publicar.
|
||||
fn run_repo_sign(
|
||||
repo_dir: &std::path::Path,
|
||||
@@ -2432,6 +2494,7 @@ fn build_export_mutations(
|
||||
expected_hash: Some(art_hash),
|
||||
deps: recipe.deps.clone(),
|
||||
evidence: recipe.evidence.clone(),
|
||||
slots: recipe.slots.clone(),
|
||||
});
|
||||
stats.source_patches += 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user