Skip to content

feat(infra): verify subcommand + post-seal self-check for config fixtures - #405

Merged
eval-exec merged 1 commit into
mainfrom
infra/inventory-verify
Sep 20, 2026
Merged

eval-exec merged 1 commit into
mainfrom
infra/inventory-verify

Conversation

@eval-exec

Copy link
Copy Markdown
Owner

What

Closes the loop on the seal's fail-loudly contract with an inventory:

  • At seal time every file under the fixture root is hashed (sha256) into INVENTORY.json, then the self-check re-walks and compares — a bootstrap that wrote after the inventory was taken fails materialization loudly instead of shipping a drifted fixture.
  • infra verify doom|spacemacs — new CLI subcommand running the deep check on demand: re-hashes the whole tree and compares against the sealed inventory, reporting missing/modified/added paths.
  • copy_tree's symlink branch routed through the cfg-gated symlink() helper (the one unix-only call site the earlier gating pass missed).

Verification

  • infra verify doom on the live fixture: verified clean
  • infra status: both fixtures sealed
  • Workspace tests compile clean, fmt clean

…ures

Closes the loop on the seal's 'fail loudly' contract with an
inventory: at seal time every file under the fixture root is hashed
(sha256) into INVENTORY.json; a new verify_deep re-walks and compares.
manifest_and_seal now self-checks after sealing so a bootstrap that
wrote after the inventory was taken fails materialization loudly
instead of shipping a drifted fixture.

infra verify <name> runs the deep check on demand.  Wired for doom;
spacemacs rides the same trait method.  This is the Bazel
MANIFEST-as-invariant pattern: the fixture's own manifest is the
proof its sessions mounted what they claim.
Copilot AI balanced review requested due to automatic review settings September 20, 2026 08:19
@eval-exec
eval-exec merged commit a64d2a6 into main Sep 20, 2026
31 of 35 checks passed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

The inventory is never persisted, so the post-seal self-check and infra verify both compare the tree against a freshly-derived inventory of the same tree and can never detect drift, and the CLI omits the advertised spacemacs target.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 3 High severity · 1 Medium severity · 1 Low severity

Open (5)
What changed in this PR

This PR adds a content-inventory mechanism to neomacs-infra intended to prove that a sealed test fixture (Doom/Spacemacs) is byte-identical to when it was sealed. It introduces a new inventory module (sha256 hashing + drift comparison), a verify_deep method on the ConfigEnvironment trait, a post-seal self-check in manifest_and_seal, and an infra verify <env> CLI subcommand. It fits into the existing fixture "seal / fail-loudly" model that guards shared editor test environments.

Changes:

  • New inventory.rs: Inventory/Entry/Drift types, tree walk + sha256 hashing, JSONL (de)serialization, and verify_deep.
  • manifest_and_seal gains a post-seal self-check; verify_deep added to the trait and both env impls; new infra verify CLI arm.
  • Adds sha2 dependency to neomacs-infra (Cargo.toml + Cargo.lock).
File Description
crates/​neomacs-infra/​src/​config_env/​inventory.rs New inventory module: hashing walk, JSONL, and drift comparison (walk logic duplicated between build and verify_deep).
crates/​neomacs-infra/​src/​config_env/​common.rs Post-seal self-check + build_inventory; self-check compares tree to a fresh in-memory inventory of itself, so it can never fail.
crates/​neomacs-infra/​src/​config_env/​mod.rs Exports inventory types and adds verify_deep to the trait.
crates/​neomacs-infra/​src/​config_env/​doom.rs Implements verify_deep by rebuilding the baseline from the live tree (always clean).
crates/​neomacs-infra/​src/​config_env/​spacemacs.rs Same verify_deep implementation as Doom (always clean).
crates/​neomacs-infra/​src/​bin/​infra.rs Adds infra verify subcommand, but only wires up doom, not spacemacs.
crates/​neomacs-infra/​Cargo.toml, Cargo.lock Adds the sha2 workspace dependency.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +287 to +303
///
/// The seal runs last so the INVENTORY's hashes describe the final sealed
/// bytes; the INVENTORY itself is written before sealing so `seal`'s walk
/// covers it too.
pub fn manifest_and_seal(root: &Path, name: &str, source_note: &str) -> Result<(), String> {
fs::write(
root.join("MANIFEST"),
format!("name = {name}\nsource = {source_note}\n"),
)
.map_err(|error| format!("write MANIFEST: {error}"))?;
seal(root)
seal(root)?;
// Self-check: the sealed fixture must verify clean against its own
// fresh inventory. A drift here means the bootstrap wrote after the
// inventory was taken, or the seal missed a path -- fail loudly now,
// not six hours later in a mysterious parity divergence.
let inventory = super::inventory::Inventory::build(root)?;
let drift = super::inventory::verify_deep(root, &inventory)?;
Comment on lines +218 to +220
fn verify_deep(&self) -> Result<crate::config_env::inventory::Drift, String> {
let inventory = crate::config_env::inventory::Inventory::build(&self.root)?;
crate::config_env::inventory::verify_deep(&self.root, &inventory)
Comment on lines +230 to +232
fn verify_deep(&self) -> Result<crate::config_env::inventory::Drift, String> {
let inventory = crate::config_env::inventory::Inventory::build(&self.root)?;
crate::config_env::inventory::verify_deep(&self.root, &inventory)
Comment on lines +33 to +57
Some("verify") => match args.next().as_deref().and_then(|arg| arg.to_str()) {
Some("doom") => match neomacs_infra::config_env::DoomEnvironment::open() {
Some(environment) => {
match neomacs_infra::config_env::ConfigEnvironment::verify_deep(&environment) {
Ok(drift) if drift.is_clean() => println!("doom: verified clean"),
Ok(drift) => {
eprintln!("doom fixture DRIFTED: {drift:?}");
exit(1);
}
Err(error) => {
eprintln!("doom verify error: {error}");
exit(1);
}
}
}
None => {
eprintln!("doom: not materialized");
exit(1);
}
},
other => {
eprintln!("unknown environment: {other:?}");
exit(2);
}
},
Comment on lines +139 to +173
// Current tree state, walked and hashed the same way.
let mut current: Vec<(String, u64, String)> = Vec::new();
fn walk(root: &Path, dir: &Path, out: &mut Vec<(String, u64, String)>) -> Result<(), String> {
let items =
fs::read_dir(dir).map_err(|error| format!("read {}: {error}", dir.display()))?;
let mut items: Vec<_> = items.filter_map(Result::ok).map(|e| e.path()).collect();
items.sort();
for path in items {
let meta = fs::symlink_metadata(&path)
.map_err(|error| format!("stat {}: {error}", path.display()))?;
let rel = path
.strip_prefix(root)
.map_err(|error| format!("relativize {}: {error}", path.display()))?
.to_string_lossy()
.into_owned();
if meta.is_symlink() {
let target = fs::read_link(&path)
.map_err(|error| format!("readlink {}: {error}", path.display()))?;
out.push((rel, 0, format!("link:{}", target.to_string_lossy())));
} else if meta.is_dir() {
walk(root, &path, out)?;
} else {
let bytes =
fs::read(&path).map_err(|error| format!("read {}: {error}", path.display()))?;
let digest = Sha256::digest(&bytes);
let mut hex = String::with_capacity(digest.len() * 2);
for byte in digest {
hex.push_str(&format!("{byte:02x}"));
}
out.push((rel, bytes.len() as u64, hex));
}
}
Ok(())
}
walk(root, root, &mut current)?;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants