test(build): close resource extraction mutants

Prove fail-closed slot and handler-parameter diagnostics through AppBuilder, consolidate resource insertion around the stored entry, and lower validated runtime events without redundant fallible identifier conversion.

req: build/004

req: diagnostics/004

req: surface/008

req: test/021
This commit is contained in:
slhx agent
2026-07-17 03:57:29 +02:00
parent b28ffacacb
commit 72bce73e52
2 changed files with 63 additions and 53 deletions
+1 -1
View File
@@ -21,7 +21,7 @@
- [ ] **State:** In progress — the package-native capped xtask entry point is reachable, rejects unknown packages, propagates mutest failure, and mutation-tests `hemx-axum`, `hemx-core`, `hemx-js`, and the full `hemx-test` package cleanly; full package closure remains. - [ ] **State:** In progress — the package-native capped xtask entry point is reachable, rejects unknown packages, propagates mutest failure, and mutation-tests `hemx-axum`, `hemx-core`, `hemx-js`, and the full `hemx-test` package cleanly; full package closure remains.
- **User value:** maintainers can run one bounded repository command and trust that meaningful Rust logic across every mutation-applicable library is either killed or explicitly justified. - **User value:** maintainers can run one bounded repository command and trust that meaningful Rust logic across every mutation-applicable library is either killed or explicitly justified.
- **Build:** add a capped `hemx-xtask` mutation command that invokes `/opt/repositories/mutest`/`mutest` through package-native test targets rather than the broken workspace-wide example path; enumerate only current mutation-applicable library/proc-macro packages; finish adversarial tests or simplify code until every survivor is classified; keep equivalent, invariant-only, and infrastructure-inapplicable classifications inspectable and minimal; document the exact local release command in the existing readiness surface. - **Build:** add a capped `hemx-xtask` mutation command that invokes `/opt/repositories/mutest`/`mutest` through package-native test targets rather than the broken workspace-wide example path; enumerate only current mutation-applicable library/proc-macro packages; finish adversarial tests or simplify code until every survivor is classified; keep equivalent, invariant-only, and infrastructure-inapplicable classifications inspectable and minimal; document the exact local release command in the existing readiness surface.
- **Blocked by:** none; broad survivors currently remain in `hemx-build`, `hemx-derive`, and `hemx-lsp` outside already-clean focused contracts. The current `hemx-build` frontier now adversarially proves static convention diagnostics; public file/source diagnostics and context facts; generated-artifact refresh; client-handler rejection; and exact class/form extraction, field metadata, invalid-name, and collision behavior through `AppBuilder::run`; remaining event/slot/atom/handle extraction and internal diagnostic survivors remain. The complete 470-mutant `hemx-axum` package gate now passes with 262 caught and 208 unviable after public page/form/multipart/registry/response/runtime proofs and narrow classification of infallible header parsing and streamed multipart unwrap-equivalent mutants. - **Blocked by:** none; broad survivors currently remain in `hemx-build`, `hemx-derive`, and `hemx-lsp` outside already-clean focused contracts. The current `hemx-build` frontier now adversarially proves all class/form/event/slot/atom/handle extraction, field/parameter metadata, invalid-name, collision, deduplication, and keyed-upgrade behavior through `AppBuilder::run`; resource extraction is mutation-clean, leaving only internal diagnostic/code-generation survivors before the full package gate. The complete 470-mutant `hemx-axum` package gate now passes with 262 caught and 208 unviable after public page/form/multipart/registry/response/runtime proofs and narrow classification of infallible header parsing and streamed multipart unwrap-equivalent mutants.
- **Proof:** the new xtask mutation command exits zero within its documented bound, covers each applicable package, emits no unexplained missed mutant, and a deliberate adjacent mutation makes it fail. `cargo run -p hemx-xtask -- test` remains green. req: test/020 req: test/021 - **Proof:** the new xtask mutation command exits zero within its documented bound, covers each applicable package, emits no unexplained missed mutant, and a deliberate adjacent mutation makes it fail. `cargo run -p hemx-xtask -- test` remains green. req: test/020 req: test/021
## 3. Elect and enforce the release license policy ## 3. Elect and enforce the release license policy
+62 -52
View File
@@ -335,7 +335,7 @@ impl Resources {
if let Some(on_attr) = static_attr(&node.attrs, "data-hemx-on") { if let Some(on_attr) = static_attr(&node.attrs, "data-hemx-on") {
for event in event_tokens(&on_attr) { for event in event_tokens(&on_attr) {
let canonical = canonical_symbol(root, path, event); let canonical = canonical_symbol(root, path, event);
self.insert_event(canonical, event.to_owned(), component.clone())?; self.insert_event(canonical, event.to_owned(), component.clone());
} }
} }
@@ -417,15 +417,17 @@ impl Resources {
component: String, component: String,
keyed: bool, keyed: bool,
) -> io::Result<()> { ) -> io::Result<()> {
insert_resource(&mut self.slots, "slot", symbol, name, component, keyed) let slot = insert_resource(&mut self.slots, "slot", symbol, name, component)?;
slot.keyed |= keyed;
Ok(())
} }
fn insert_handle(&mut self, symbol: String, name: String, component: String) -> io::Result<()> { fn insert_handle(&mut self, symbol: String, name: String, component: String) -> io::Result<()> {
insert_resource(&mut self.handles, "handle", symbol, name, component, false) insert_resource(&mut self.handles, "handle", symbol, name, component).map(|_| ())
} }
fn insert_atom(&mut self, symbol: String, name: String, component: String) -> io::Result<()> { fn insert_atom(&mut self, symbol: String, name: String, component: String) -> io::Result<()> {
insert_resource(&mut self.atoms, "atom", symbol, name, component, false) insert_resource(&mut self.atoms, "atom", symbol, name, component).map(|_| ())
} }
fn insert_class(&mut self, symbol: String, token: String, component: String) -> io::Result<()> { fn insert_class(&mut self, symbol: String, token: String, component: String) -> io::Result<()> {
@@ -482,32 +484,13 @@ impl Resources {
Ok(()) Ok(())
} }
fn insert_event(&mut self, symbol: String, name: String, component: String) -> io::Result<()> { fn insert_event(&mut self, symbol: String, name: String, component: String) {
let ident = event_ident(&name).ok_or_else(|| { self.events.entry(symbol.clone()).or_insert(EventToken {
io::Error::new(
io::ErrorKind::InvalidData,
format!(
"invalid hemx event `{name}`; expected an ASCII event name usable from Rust"
),
)
})?;
let event = EventToken {
symbol, symbol,
ident, ident: name.clone(),
component, component,
name, name,
}; });
match self.events.get(&event.symbol) {
Some(existing) if existing.name != event.name => Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("conflicting event name for `{}`", existing.symbol),
)),
Some(_) => Ok(()),
None => {
self.events.insert(event.symbol.clone(), event);
Ok(())
}
}
} }
fn insert_form( fn insert_form(
@@ -1323,32 +1306,29 @@ fn __hemx_attr(tag: &str, attr: &str) -> Option<::std::string::String> {
} }
} }
fn insert_resource( fn insert_resource<'a>(
map: &mut BTreeMap<String, Resource>, map: &'a mut BTreeMap<String, Resource>,
kind: &str, kind: &str,
symbol: String, symbol: String,
name: String, name: String,
component: String, component: String,
keyed: bool, ) -> io::Result<&'a mut Resource> {
) -> io::Result<()> { let resource = make_resource(kind, symbol, name, component)?;
let mut resource = make_resource(kind, symbol, name, component)?; match map.entry(resource.ident.clone()) {
resource.keyed = keyed; std::collections::btree_map::Entry::Occupied(entry)
match map.get_mut(&resource.ident) { if entry.get().symbol != resource.symbol =>
Some(existing) if existing.symbol != resource.symbol => Err(io::Error::new( {
io::ErrorKind::InvalidData, let existing = entry.get();
format!( Err(io::Error::new(
"duplicate generated identifier `{}` for `{}` and `{}`", io::ErrorKind::InvalidData,
resource.ident, existing.symbol, resource.symbol format!(
), "duplicate generated identifier `{}` for `{}` and `{}`",
)), resource.ident, existing.symbol, resource.symbol
Some(existing) => { ),
existing.keyed |= keyed; ))
Ok(())
}
None => {
map.insert(resource.ident.clone(), resource);
Ok(())
} }
std::collections::btree_map::Entry::Occupied(entry) => Ok(entry.into_mut()),
std::collections::btree_map::Entry::Vacant(entry) => Ok(entry.insert(resource)),
} }
} }
@@ -1570,10 +1550,6 @@ fn data_param_ident(name: &str) -> Option<String> {
rust_ident(&data_name.replace('-', "_")) rust_ident(&data_name.replace('-', "_"))
} }
fn event_ident(name: &str) -> Option<String> {
rust_ident(&name.replace(['-', ':'], "_"))
}
fn can_host_keyed_collection(tag: &str) -> bool { fn can_host_keyed_collection(tag: &str) -> bool {
matches!( matches!(
tag, tag,
@@ -2564,6 +2540,40 @@ mod tests {
invalid_handler.display() invalid_handler.display()
) )
); );
std::fs::remove_file(invalid_handler).unwrap();
std::fs::write(
templates.join("invalid_param.heml"),
r#"<button data-hemx-handle="save" data-123="value">Save</button>"#,
)
.unwrap();
let error = app()
.template_dir(&templates)
.out_dir(&invalid_out)
.run()
.unwrap_err();
assert_eq!(error.kind(), io::ErrorKind::InvalidData);
assert_eq!(
error.to_string(),
"invalid handler param attribute `data-123`; expected data-* name usable from Rust"
);
std::fs::remove_file(templates.join("invalid_param.heml")).unwrap();
std::fs::write(
templates.join("invalid_slot.heml"),
r#"<section data-hemx-slot="123">Invalid</section>"#,
)
.unwrap();
let error = app()
.template_dir(&templates)
.out_dir(&invalid_out)
.run()
.unwrap_err();
assert_eq!(error.kind(), io::ErrorKind::InvalidData);
assert_eq!(
error.to_string(),
"invalid hemx slot name `123`; expected a Rust identifier"
);
let _ = std::fs::remove_dir_all(root); let _ = std::fs::remove_dir_all(root);
// test req: build/004 req: client_local/011 req: diagnostics/004 // test req: build/004 req: client_local/011 req: diagnostics/004
} }