diff --git a/PLAN.md b/PLAN.md index 9d40f5f..eddeaa1 100644 --- a/PLAN.md +++ b/PLAN.md @@ -21,7 +21,7 @@ - [ ] **State:** In progress — the package-native capped xtask entry point is reachable, rejects unknown packages and invalid shards, propagates mutest failure, and mutation-tests `hemx-axum`, `hemx-build`, `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. - **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-derive` and `hemx-lsp` outside already-clean focused contracts. The xtask mutation runner now creates shard output parents before invoking mutest, fixing first-use failure for newly sharded packages. `hemx-derive` public shards `1/8` through `4/8` are clean. Shard `4/8` passed with 61 mutants (49 caught, 12 unviable) after exact required/optional/multiple form-decoding code generation plus bare/qualified/mismatched/empty generic container boundaries. Shards `5/8` through `8/8` remain. The mutation entry point accepts validated one-based `SHARD/TOTAL` operands, maps them to native zero-based shards, uses shard-specific output directories, grants repo-owned compiler probes a 120-second floor, and preserves the unsharded gate. All eight deterministic `hemx-build` shards now pass: 1,304 mutants total, 1,070 caught and 234 unviable, including final shard `8/8` with 159 mutants (139 caught, 20 unviable). The complete 470-mutant `hemx-axum` package gate 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-derive` and `hemx-lsp` outside already-clean focused contracts. The xtask mutation runner now creates shard output parents before invoking mutest, fixing first-use failure for newly sharded packages. `hemx-derive` public shards `1/8` through `5/8` are clean. Shard `5/8` passed with 61 mutants (57 caught, 4 unviable) after fail-closed generated field-name validation and deletion of a redundant empty-parameter branch. Shards `6/8` through `8/8` remain. The mutation entry point accepts validated one-based `SHARD/TOTAL` operands, maps them to native zero-based shards, uses shard-specific output directories, grants repo-owned compiler probes a 120-second floor, and preserves the unsharded gate. All eight deterministic `hemx-build` shards now pass: 1,304 mutants total, 1,070 caught and 234 unviable, including final shard `8/8` with 159 mutants (139 caught, 20 unviable). The complete 470-mutant `hemx-axum` package gate 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 ## 3. Elect and enforce the release license policy diff --git a/hemx-derive/src/lib.rs b/hemx-derive/src/lib.rs index 1ca4e59..800cc40 100644 --- a/hemx-derive/src/lib.rs +++ b/hemx-derive/src/lib.rs @@ -670,12 +670,8 @@ fn rust_ident(name: &str) -> Option { } fn missing_handle_params(path: &PathBuf, ident: &str, function: &ItemFn) -> Vec { - let required = handle_params(path, ident); - if required.is_empty() { - return Vec::new(); - } let args = handler_arg_names(function); - required + handle_params(path, ident) .into_iter() .filter(|param| !args.contains(param)) .collect() @@ -1029,7 +1025,7 @@ mod tests { handler_syms_path, has_form_param, has_non_unit_return, is_type_named, join_contract_errors, missing_component_handlers, missing_form_generated_files_message, missing_handle_params, missing_handler_generated_files_message, parser_type, - returns_result, syms_contains_handle, HandlerPlacement, + returns_result, rust_ident, syms_contains_handle, HandlerPlacement, }; use quote::{quote, ToTokens}; use syn::{parse_quote, ItemFn, Type}; @@ -1432,6 +1428,16 @@ mod tests { assert!(form_model_type(&foreign_form).is_none()); } + #[test] + fn generated_form_field_names_map_only_to_valid_rust_identifiers() { + assert_eq!(rust_ident("first-name").as_deref(), Some("first_name")); + assert_eq!(rust_ident("_private2").as_deref(), Some("_private2")); + for invalid in ["", "2fast", "with space", "naïve"] { + assert_eq!(rust_ident(invalid), None, "{invalid:?} must fail closed"); + } + // test req: form/004 req: diagnostics/003 + } + #[test] fn handler_params_match_generated_param_names() { let path = std::env::temp_dir().join("hemx-derive-param-test.syms"); @@ -1453,6 +1459,7 @@ mod tests { assert!(missing_handle_params(&path, "show", &complete).is_empty()); assert_eq!(missing_handle_params(&path, "show", &missing), vec!["mode"]); + assert!(missing_handle_params(&path, "unknown", &missing).is_empty()); let _ = std::fs::remove_file(path); }