You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Codegen::cfg::expand_configured_features, which resolves a configured feature list through the core crate's own [features] table (transitively, skipping dep: and crate/feature tokens) and falls back to the list verbatim when the core manifest cannot be read. The JNI shim generator uses it for both its default-target feature set and each per-target override, so gate evaluation agrees with the manifest alef itself scaffolds.
BuildAndroidJniLibs derives its target list from [crates.kotlin_android] abis (the same list that scaffolds the jniLibs/<abi>/ directories) and its manifest from [crates.jni] crate_dir, so the directories alef creates and the directories alef fills cannot name two different sets.
Added: buildAndroidJniLibs derives its target list from [crates.kotlin_android] abis (the same list that scaffolds the jniLibs/<abi>/ directories) and its manifest from [crates.jni] crate_dir, so the directories alef creates and the directories alef fills cannot name two different sets.
Added: codegen::cfg::expand_configured_features, which resolves a configured feature list through the core crate's own [features] table (transitively, skipping dep: and crate/feature tokens) and falls back to the list verbatim when the core manifest cannot be read. The JNI shim generator uses it for both its default-target feature set and each per-target override, so gate evaluation agrees with the manifest alef itself scaffolds.
Add [e2e.call(s).*.overrides.java] module validation: warns when the value's last dot-segment starts with an uppercase letter (looks like a Java class, not a package) — the reported regression that produced import io.xberg.Xberg.*; in generated snippets.
Add [e2e.call(s).*] module / overrides.go.module validation: warns when the effective Go import path (override, then [go].module, then the base field) is a bare word with no . or /, since only the standard library resolves that way and this field never names it.
Add fixture args vs. IR signature validation: warns when a fixture's effective args (its own, or its resolved call's) name a parameter the Rust function/method signature does not declare, or omit a required parameter with no default. Resolves through the same CallIr/TargetParams seam e2e codegen already uses for argument type lowering, so it silently no-ops when the call is unresolvable or the resolved function is binding_excluded rather than claiming a false positive.
Both new checks land as warnings only, not errors — see src/e2e/validate_call_module.rs and src/e2e/validate_call_args.rs doc comments for the consumer-fleet measurements behind that choice.
alef snippets audit accepts --config and gained a curated-versus-generated accounting pass: a snippet under an audited root that no coverage ledger records as generated and no curated_snippets declaration claims is reported as UnaccountedSnippet (warning), a declared file is reported positively as curated, and a declaration that claims a path alef generates is an error. The pass is named as skipped, rather than silently omitted, when --config is unset or no coverage ledger records anything as generated.
alef snippets check carries the same accounting through its configured audit pass.
Added [crates.e2e.snippets].curated_snippets: glob patterns (relative to output) declaring hand-authored snippet files as curated on purpose rather than alef-generated. Resolved into SnippetGenerationReport::curated_paths and into migration::MigrationEntry::curated, so both the generation report and alef e2e snippets-migrate can distinguish a declared, intentional absence of a generated equivalent from a genuine coverage gap.
Implemented render_snippet_body for the brew (shell) e2e code generator: documentation snippets for CLI-based bindings now render a single binary subcommand "<url>" --flags line, built from the same call-config resolution the executable brew e2e suite already uses.
Add [crates.verify].ignore_ephemeral, a glob-pattern opt-out so alef verify never reports intentionally ephemeral, gitignored generated output (e.g. registry-mode test_apps/) as a permanent "missing generated files" failure; every excluded path is still counted and reported in alef verify's coverage output.
Added [crates.e2e.snippets].sample_base_url: the public base URL generated documentation snippets bind for a fixture's mock_url / mock_url_list arguments. It is documentation-only — the executable e2e suite keeps binding the per-fixture mock server — so a project can publish snippets a reader can actually run without changing what its tests talk to. Relative fixture paths ("/pdf/report.pdf") resolve against the mock server for tests and against the configured host for docs, from the same fixture, with no per-fixture edit. An explicit $mock_url placeholder resolves against it too.
Add [crates.node].excluded_default_features; scaffold_node_cargo now drops excluded names from both the wrapper's own [features] default = [...] array and the core dependency's explicit features = [...] line, matching the fix already shipped for Ruby/Swift/Dart. Same defect: a target_dep_overrides entry excluding a feature for one cfg target was defeated by the wrapper's own unconditional default forwarding.
Add [crates.elixir].excluded_default_features; scaffold_elixir_cargo fixed the same way.
Add [crates.php].excluded_default_features; scaffold_php_cargo fixed the same way. The function-gated feature set PHP must always request (php_function_gated_core_features_to_add) is deliberately NOT filtered against the exclusion -- those are hard compile-time requirements of an unconditionally-emitted function, not a default-features convenience.
Add [crates.ffi].excluded_default_features; effective_ffi_default_features (the single derivation scaffold_ffi and warn_on_ffi_feature_drift both read) now excludes these names from both the FFI crate's own [features] default = [...] list and the core dependency's explicit features = [...] line, while still declaring them so cargo build --features <name> keeps working.
Added: a warning when a field path declared in [e2e].fields, fields_optional, fields_array, fields_method_calls or result_fields is refused for a target because that target's result type declares no such member. The warning names the field, the target language and the config key that declares it. Paths nobody declared — assertion groupings, streaming pseudo-fields, virtual namespace prefixes — stay silent, and no target fails its build over a per-target shape difference.
alef snippets gaps now prints a gap-coverage report on every run — snippet roots and files discovered, documentation roots and pages actually opened, references found versus supplied by configuration, and required languages against snippet groups compared — so a "No gaps found." result can no longer read as a wider claim than the check made. A consumer that omitted required_languages, docs_dirs and include_base_paths from its alef.toml previously read a clean gap report for a run in which the language-parity check never executed and not one documentation page was opened.
alef snippets gaps now names every unset input (docs_dirs/--docs, required_languages/-L, include_base_paths/--include-base-path) together with the check class its absence disables.
alef snippets gaps gained --strict, which fails the run when an unset input left a check class with nothing to compare, so a CI job whose purpose is gap detection cannot go green by being unconfigured. An unset include_base_paths is reported but deliberately not strict-fatal: it makes include targets over-report rather than manufacture a false clean.
alef verify now reports its own coverage on every run. Every finding verify produces is a negative claim, so a green result was indistinguishable from a run that examined nothing -- and consumer CI reads it under job names like "Alef-generated bindings freshness" as a whole-tree freshness gate. It is a far narrower claim: only files carrying an alef marker on disk are held to a hash; markerless generated output (.json, .jar, lockfiles) is checked for PATH PRESENCE only, so a present-but-wrong file passes; and anything outside the ownership walk's scan set is never opened at all. Each run now prints the managed surface split into content-verified / present-but-not-content-verified / absent, the files opened versus never examined, unmarked create-once seeds, and marked files the surface does not claim. Follows the alef snippets audit precedent of naming the check class a run skipped instead of printing a bare clean result.
Added: [crates.ruby].excluded_default_features, mirroring SwiftConfig/DartConfig. scaffold_ruby_cargo previously forwarded every collect_cfg_features name into the generated wrapper crate's [features] default = [...] array unconditionally, which re-enabled a feature a [crates.ruby].target_dep_overrides entry excluded for a specific cfg target one layer down (Cargo unions feature requests across every dependency edge to the same resolved package regardless of target). The excluded name stays declared (so cargo build --features <name> keeps working) but is dropped from default and from the core dependency's own explicit features = [...] line.
The gating itself is unchanged, and is now pinned by tests rather than argued from doc comments. For an unmarkable seed (LICENSE, mvnw, gradlew, .gitkeep -- paths marker_comment_style answers None for), alef adopt --write --clobber-create-once-seeds writes no byte of the file: stamp_for yields None, so the entire adoption is one entry in the committed .alef-ownership.toml. That entry is precisely what write_scaffold_files_report accepts as proof of ownership for an unmarkable path (owned = has_marker || (!is_markable && is_owned_by_ownership_record(..))), so the adoption is what clears the guard for the next overwriting write. Five tests in cli::commands::adopt::tests::create_once_seeds measure the bytes on both sides of the adoption and both sides of the write, including a control proving the identical overwrite: true write refuses when the adoption did not happen.
Added regression coverage: tests/cache_stage_hit_requires_intact_outputs.rs (mirrors the existing per-language tamper-detection test for the stage cache) and bin_cli::all_commands::tests::all_a_cache_hit_run_does_not_delete_its_own_manifested_binding_output (drives alef all twice over the same fixture and asserts the second, cache-hit run does not delete the first run's binding output).
Add the_emitted_cgo_preamble_defines_exactly_the_effective_ffi_default_features, which reads the -D tokens back out of the Go file the backend actually writes and compares them against effective_ffi_default_features, so a re-introduced Go-local derivation fails a test instead of shipping a preamble that disagrees with the library. Its fixture exercises a configured passthrough feature, a feature discovered only from a #[cfg(feature = ...)] gate, and a declare-only extra_features name, each pinned by a control assertion.
regression coverage: src/e2e/codegen/assertion_recipes.rs and src/e2e/codegen/rust/assertions.rs add an Envelope { results: Vec<Document> } / Document { chunks } fixture and assert both directions — a root type that genuinely declares chunks still renders the real assertion, and a root type that only reaches chunks through a different IR type does not.
Add generate_formats_a_scaffold_manifest_changed_by_a_config_only_edit to src/bin_cli/core_commands/format_scope_tests.rs, proven to fail against pre-fix code and pass against the fix.
e2e/java: added javac-backed regression coverage proving a generated doc snippet whose fixture carries a plain (non-json_object) string argument over the JVM's 65535-byte CONSTANT_Utf8 cap actually compiles, not just renders without one long literal. The underlying fix (java_string_literal chunking, task #180) was already merged; this closes the gap where only rendered-text assertions existed for the call-argument path, and includes a sanity test proving javac itself rejects an unchunked 100,000-byte literal so a pass is evidence the compiler ran.
Added tests/java_kotlin_generate_build_dir_agreement_test.rs, which runs the real JavaBackend/KotlinBackend generators and the real build_command_config_for_language resolution and asserts the build command's target directory is an ancestor of the directory sources actually landed in, for both the unconfigured default and a [crates.output] that moves the tree outside packages/<lang>.
Add snippet/e2e cross-generator agreement tests plus direct unit tests (declared key survives, undeclared key refused, serde_flatten types exempted) for the new filter.
Added regression tests in src/e2e/codegen/typescript/test_file/json_object_field_agreement_tests.rs asserting a declared nested key survives and an undeclared nested key is refused identically by both render_snippet_body and render_test_case.
Added: src/bin_cli/tree_state.rs, the classifier, compiled by build.rs via #[path] and by the crate normally, so cargo test --lib exercises the shipped code instead of a second copy of it. Its tests assert both directions — a checkout dirtied only by untracked files reports clean, and a tracked modification, deletion, or staged addition still reports dirty.
Re-audited the three still-suppressed pyrefly codes on generated api.py (bad-argument-count, not-iterable, missing-attribute, alef-334); extended the pyrefly_generated_package_tests fixture with a multi-arg native constructor, a Vec<enum> field, a nested options dataclass, and a thiserror-derived error enum to exercise each code's most plausible generated shape, and confirmed via hand-corruption that the gate genuinely detects each one (not a vacuous pass) while finding no live defect under those shapes.
Added a dedicated integration test (bin_cli::all_commands::pyrefly_generated_package_tests) that runs alef all against a real fixture and then runs real pyrefly check over the actual generated packages/python output — the first alef-side check that points pyrefly at generated package output rather than only doc snippets (snippets::validators::python remains snippet-only). Skips cleanly when pyrefly is not on PATH, matching the existing convention in snippets::validators::python's own pyrefly-backed tests.
Added a canonical ~keep rule documenting which Named-containing shapes transport per-element (Vec<Named>) versus as one JSON blob (Map<_, _>, bare Named, Optional<Named>) across the Swift trait bridge, in gen_rust_crate::plugin_inbound.
Added (swift): a two-target SwiftPM compile regression that generates the trait-bridge and box files into a fixture package with the real Client -> RustBridge dependency direction and runs swift build on it. Every previous Swift trait-bridge test asserted on emitted strings, which is how #258 shipped twice; this gate fails on macOS when the Swift toolchain is missing and skips loudly elsewhere rather than reporting a silent pass.
Extended the two-target SwiftPM compile gate with DTO types nested in containers — Vec<Named> in return and parameter position, and Option<Named> in return position.
Changed
The Android ABI cross-compile is gated at execution, not configuration, so no build that works today starts requiring an NDK. It runs only for assembleRelease/publishAndReleaseToMavenCentral task graphs, only for ABIs whose library is not already staged (a publish workflow that unpacks prebuilt libraries keeps working on a runner with no NDK), and never under -Palef.skipAndroidJni=true. A missing cargo-ndk fails with a message naming the tool, the install command and the opt-out instead of a bare exec failure.
Fold the Go backend's cgo -D feature-macro derivation onto codegen::cfg::effective_ffi_default_features. backends::go::cgo_features carried a private ffi_default_features that duplicated the centralized derivation line for line, so an edit to one silently left the other behind and the emitted cgo preamble could stop describing the cdylib it links. Only the Go-specific half stays local: the cbindgen macro-name mangling and the -D formatting.
Add the_emitted_cgo_preamble_defines_exactly_the_effective_ffi_default_features, which reads the -D tokens back out of the Go file the backend actually writes and compares them against effective_ffi_default_features, so a re-introduced Go-local derivation fails a test instead of shipping a preamble that disagrees with the library.
Changed: the Android ABI cross-compile is gated at execution, not configuration, so no build that works today starts requiring an NDK. It runs only for assembleRelease/publishAndReleaseToMavenCentral task graphs, only for ABIs whose library is not already staged (a publish workflow that unpacks prebuilt libraries keeps working on a runner with no NDK), and never under -Palef.skipAndroidJni=true. A missing cargo-ndk fails with a message naming the tool, the install command and the opt-out instead of a bare exec failure.
docs.snippets validation now fails fast, before any toolchain runs, when a language needs a compiled artifact (compile/typecheck/run) but has no configured session that could plausibly have produced one yet -- no session at all, an ambiguous session, or a session with an empty before list. Warns always; under strict, bails immediately instead of spending an hour validating snippets that were doomed from the start (GH #256).
alef snippets check --lang <language> (and any other filtered run_validation call) now prepares only the configured sessions its filtered snippet set actually needs, instead of running every configured before build hook regardless of the filter -- a single-language diagnostic no longer pays for every other language's build. Sessions sharing a working directory with a needed one are still prepared together, so the scratch sweep never treats a cohabiting session's live build cache as abandoned.
Changed: languages served from the cache are now reported at the default verbosity (<lang>: unchanged since the last run by this alef build, skipping). A fully cached run previously printed only Generated 0 files, which reads as "nothing needed changing" when it means "nothing was looked at".
Changed: every .alef/ cache key that can skip work is now a CacheKey, constructible only inside cli::cache_identity, where each constructor folds in the alef build identity. The is_ir_cached / is_lang_cached / is_stage_cached predicates accept nothing else, so a future cache cannot gate a skip on a key that forgot the salt — it does not compile. This is the durable half: the IR cache drifted out of step with its two siblings precisely because the salt was a convention each call site had to remember.
Note for consumers: the first alef generate after upgrading to this release re-extracts the IR instead of replaying the previous release's, and regenerates any language whose outputs no longer match their stamps. Nothing is re-stamped by this change — compute_inputs_hash and CODEGEN_FORMAT_VERSION are untouched, so no file's embedded alef:hash: moves and there is no mass invalidation. The visible cost is one extraction. The visible diff is whatever real output difference a stale cache had been hiding, which on a repo that has been replaying an old surface for several releases can be large. That diff is the correct output finally landing, not a regression introduced here.
Fold the Go backend's cgo -D feature-macro derivation onto codegen::cfg::effective_ffi_default_features. backends::go::cgo_features carried a private ffi_default_features that duplicated the centralized derivation line for line, so an edit to one silently left the other behind and the emitted cgo preamble could stop describing the cdylib it links. Only the Go-specific half stays local: the cbindgen macro-name mangling and the -D formatting.
[crates.e2e.snippets].curated_snippets globs now resolve against the project root (the directory holding alef.toml), not against [crates.e2e.snippets].output. Hand-authored snippets sit beside the generated tree rather than inside it, so an output-relative pattern could not name them at all; measured across three consumer trees, all 113 hand-authored snippets lived outside output. BREAKING: an existing output-relative pattern must be rewritten with its full project-root path (docker/*.md becomes docs/snippets/docker/*.md); a pattern that no longer matches fails loudly with an error naming it, and the error explains the new base.
A curated glob is now refused if it escapes the project root or is absolute, and snippets-migrate fails when the migrated root does not lie beneath the project root, rather than silently reinterpreting every pattern in a different key space.
A curated declaration is resolved by walking only each pattern's literal directory prefix, so project-root-relative patterns do not cost a walk of the repository.
A curated_snippets pattern that matches zero files, or that matches a path alef itself generates, now fails the run instead of being silently accepted.
Changed: a snippet run that publishes the unconfigured https://example.com fallback now warns once, naming the affected fixtures and the config key that fixes it, and records them on SnippetGenerationReport::placeholder_sample_url_fixtures. Generated output is unchanged when sample_base_url is unset. An unusable sample_base_url (empty, whitespace-bearing, or scheme-less) fails generation instead of silently falling back.
Changed: render_cargo_toml in the Rust e2e generator takes a CargoTomlInputs struct instead of twelve positional arguments, six of them adjacent bools behind a too_many_arguments allow.
Changed: the build-time working-tree classifier now asks git diff --quiet HEAD — tracked paths only, index and working tree both, so a staged addition or a deletion still counts as dirty. Untracked files no longer count: reaching the compiler requires a mod/include! chain rooted at a tracked src/lib.rs, so untracked source that actually affects the build drags a tracked modification along with it. A denylist would have covered .cargo-ok and then waited for the next tool's marker file.
Changed: a repository with no commit yet now stamps unknown instead of clean. There is no HEAD to call the tree clean relative to, and clean reads as a provenanced build.
Consumers should expect a large one-time diff on their next alef all. Every generated file whose emitted shape was not already canonical is reformatted in that run. The change is formatting-only: file bodies are re-derived from the same generation inputs, alef verify is clean afterwards, and consecutive runs are byte-identical (verified across 831 files on the fixture). Review the diff once and commit it; it does not recur.
One consequence worth knowing: for a language whose generator emits non-canonical bytes, alef all now rewrites those files on every run (raw content written, formatter canonicalises it back to the same result), so the per-language "up to date (skipping)" cache no longer fires for them and the run's changed-file count no longer settles at zero. On-disk content is unaffected and stable; the cost is extra writes, not drift.
Changed the Swift JSON-bridged leaf verdict to be answered in one place for both generators: FieldResolver::swift_json_bridged_traversal_prefix and the new swift_json_bridged_iteration_prefix are two framings of one walk, and the snippet presentation resolver now asks it instead of re-deriving nothing at all.
Changed (BREAKING, swift): alef no longer emits a default-implementation extension on Swift{Trait}Bridge. The IR records that a Rust trait method has a default body but never the body itself, so every stub alef wrote was a guess — 0.67.5's unit_enum_default_case picked the first fieldless variant rather than the Rust Default, defaulted Bool methods returned true where Rust returned false, and Named returns got a "{}" literal that no DTO deserialises from. Because the inbound Rust wrapper calls the Swift shim for defaulted methods too, those guesses replaced the real Rust default at runtime instead of sitting unused. Conformers must now implement defaulted methods; each such protocol method carries a doc comment explaining why the stub is absent and what value to supply. Swift bridge conformers that relied on the generated defaults will need to add those methods.
Changed: the two TypeScript-family accessor renderings are now one derivation parameterized by map lowering (TypescriptMapAccess), so node and wasm can only disagree where the bindings genuinely differ — a NAPI HashMap is an object index, a wasm-bindgen one is a JS Mapget. An optional receiver before a get renders ?.get(...), not the element form's ?.[.
Extract scaffold::core_dep_features_excluding as the shared, generalized filter (Node/Elixir/PHP/FFI all reuse it) instead of copying a per-language <lang>_core_dep_features helper four more times.
Regenerate schemas/alef.schema.json for the four new config fields.
Correct language_excludes's doc comment and the LedgerExpectations/function_excluded_for_language comments in src/snippets/gaps.rs — they referenced a nonexistent [crates.skipped] config table, a nonexistent [workspace.crates."<name>"] per-crate override, and a nonexistent #[alef::exclude]/#[alef::opaque] attribute pair. Only #[alef::skip]/#[doc(hidden)] exist, and they act via the extraction-time binding_excluded IR flag, honored separately from and uniformly across language_excludes, not folded into it.
Clarified the [crates.docs.snippets].strict and .deny_unclassified doc comments and the alef snippets check --strict CLI help to state the unified semantics; regenerated schemas/alef.schema.json accordingly.
Split scaffold_license_files/scaffold_gitattributes out of src/scaffold/mod.rs into a new src/scaffold/generated_files.rs module to restore the file-size ratchet.
Split the Java checkstyle/javadoc scaffold tests out of src/scaffold/tests/ffi_go_java_ruby.rs into a new src/scaffold/tests/java_checkstyle.rs module.
Split the marker-detection unit tests out of src/core/hash/tests.rs into a new src/core/hash/tests/marker_detection.rs module.
Split the Kotlin bracket-wildcard assertion tests out of src/e2e/codegen/kotlin/tests.rs into a new src/e2e/codegen/kotlin/tests/wildcard.rs module.
Split the e2e-generator-defer regression tests out of src/bin_cli/all_commands_tests.rs into a new src/bin_cli/all_commands_e2e_defer_tests.rs module.
Split alef all's pre-flight/helper functions out of src/bin_cli/all_commands.rs into a new src/bin_cli/all_commands_run_setup.rs module.
Split the Commands::Generate match-arm body out of src/bin_cli/core_commands.rs into a new src/bin_cli/core_commands/generate.rs module; retargeted strict_formatting_tests.rs's source-scan test at the new file.
Split the inline unit-test module out of src/e2e/codegen/presentation.rs into a new src/e2e/codegen/presentation/tests.rs module.
Split the inline unit-test module out of src/scaffold/languages/ffi.rs into a new src/scaffold/languages/ffi/tests.rs module.
All nine splits are pure code movement with no behavior change; #[test] fn counts and pass counts are unchanged before and after each split.
Split the 11 over-cap e2e codegen files that grew past their file-size ratchet ceilings (rust/assertions.rs, go/assertions.rs, go/tests.rs, go/test_function.rs, csharp.rs, csharp/assertions.rs, elixir/assertions.rs, elixir/test_case.rs, java/assertions.rs, python/assertions.rs, typescript/assertions.rs), moving self-contained test modules and one pure data-prep helper into sibling files with no behavior change.
Dropped the stale elixir/test_case.rs entry from tests/file_size_baseline.txt after the split took the file under the 1,000-line cap, per the ratchet's own stale-entry rule; no other baseline ceilings were changed.
Extracted the declared-key check into a shared refuse_undeclared_json_keys helper in src/e2e/codegen/typescript/test_file/builders/mod.rs and routed both ts_builder_expression_inner and node_value_expression through it, so the two call sites cannot drift apart. Both accept a field keyed by its Rust name or its wire name (#[serde(rename)] / rename_all), and skip types with a serde_flatten field or with no matching TypeDef (external/opaque types).
Renamed OcrBackend/PostProcessor fixture trait names to SampleBackend/SampleTransformer in tests/backends_swift_trait_bridge_snapshot.rs, replacing a real consumer's domain vocabulary with neutral fixture names (no behavior change).
Split per-language exclusion predicates out of e2e::snippets::mod into a new e2e::snippets::exclusions module to stay under the repo's per-file line cap.
Replaced the vague "never diagnosed" comment on the scaffolded pyproject.toml's [tool.pyrefly.sub-config] suppression block with the evidence gathered above and an explicit note of which pyo3 backend surfaces (service_api decorators, trait_bridge visitors, streaming adapters, capsule types) remain unaudited for these codes.
Corrected the binding-audit-pattern ai-rulez rule (alef repo, .ai-rulez/rules/binding-audit-pattern.md), which told auditors to check config/attribute surfaces that do not exist in alef's schema.
Corrected .ai-rulez/skills/binding-audit/SKILL.md to remove fictional config/attribute surfaces (#[alef::exclude], #[alef::opaque], [workspace.exclude_types], [crates.opaque_types], [workspace.crates."<name>"]) and align it with the already-fixed binding-audit-pattern rule: real surfaces are [crates.exclude], per-language exclude_types/exclude_functions on [crates.<lang>], workspace-only [workspace.opaque_types] (a type remap, not an exclusion), and attribute-level #[alef::skip]/#[doc(hidden)] only.
Documented that binding_excluded is honored independently by every downstream consumer and that language_excludes never consults it, so audit tooling can misclassify a skipped item as a gap (alef-task #329).
Split src/snippets/gaps.rs unit tests into src/snippets/gaps/tests.rs, dropping the file under the repository's 1,000-line cap and removing its file-size ratchet baseline entry.
The fix is generic and IR-derived, not a hard-coded field list: FieldResolver::accessor now dispatches "wasm" through the same render_typescript_with_optionals renderer "node" uses (parameterized only by TypescriptMapAccess, the one real lowering difference — NAPI HashMap as object index vs. wasm-bindgen Map.get). Optionality itself comes from FieldDef.optional in the IR (FieldResolver::ir_field_sets / with_ir_result_fields), merged with any configured fields_optional, not from field-name matching. Confirmed against the tree-sitter-language-pack consumer: ProcessResult.data: Option<DataNode> in crates/ts-pack-core/src/intel/types.rs, with NO fields_optional entry for data anywhere in its alef.toml — the exact IR-only shape the fix must cover, and does.
Added regression coverage pinning this: src/e2e/codegen/presentation/wasm_optional_leaf_field_tests.rs (reproduces the ProcessResult { data: Option<DataNode> } shape at the resolve() level: bare field unguarded, nested data.kind/data.children chained with ?., node/wasm agreement, and a negative control that a required field is not over-chained) and src/snippets/validators/wasm_optional_chain_tsc_tests.rs (compiles the actual accessor shapes with real tsc under strict: the guarded shape passes, the exact unguarded shape alef 0.67.5 published fails with TS18048/TS2532, and a required field passes without ?.). A render-only assertion cannot see TS18048; these tsc-backed tests can.
Fixed
Alef e2e snippets-migrate now honors [crates.e2e.snippets].curated_snippets. The command called the curated-unaware migration::compare_root, so every hand-authored file reported as no_generated_equivalent regardless of configuration and a consumer could not tell a declared, intentional curated snippet from a genuine coverage gap. The comparison and its report rendering moved to bin_cli::snippet_migration, which routes through compare_root_curated.
The snippets-migrate text report gives a declared curated file its own curated label instead of no_generated_equivalent, matching the curated flag the --json report already carried per entry.
Migration::compare_root keyed existing files against existing_root and generated files against the configured output, so when output is a subdirectory of the migrated tree (alef e2e snippets-migrate docs/snippets against output = "docs/snippets/generated") the two key spaces were disjoint by construction and every file alef itself had generated reported as no_generated_equivalent. One consumer saw 7796 false positives against 3 genuinely hand-authored files. The generated keys now carry the nested prefix so both sides key off existing_root; parallel roots are unchanged.
A [[crates.jni.target_dep_overrides]] entry naming a core-crate aggregate feature (the android-target shape) now satisfies the #[cfg(feature = "…")] gates of every member that aggregate enables. Feature satisfaction matched configured names literally, with only full hard-coded as a universal umbrella, so a shim gated on a member of the configured aggregate was emitted behind #[cfg(not(any(<target>)))] and silently disappeared from the cross-compiled Android artifact while every desktop target kept it.
The Kotlin Android build contract never produced the per-ABI native libraries its own release guard demands. alef build --release ran gradle assembleRelease, which fails validateJniLibsForRelease because src/main/jniLibs/<abi>/lib<crate>_jni.so was scaffolded as an empty directory and nothing ever filled it — the only native build in the contract, buildHostJni, produces a host-architecture library that can never satisfy an Android ABI directory. The generated build.gradle.kts now emits a buildAndroidJniLibs task that cross-compiles the JNI crate with cargo-ndk straight into src/main/jniLibs/, wired ahead of both the release guard and AGP's jniLibs merge.
[Crates.include] entries that match nothing now fail extraction instead of emptying the binding: include is an allowlist, so an entry naming no extracted item did not fail open — it dropped every type and enum from the surface while alef build still exited 0 and generated empty bindings. A typo (include.types = ["Kpet"]) or the qualified crate::path::Type spelling that [crates.exclude].types accepts both produced an empty type list silently. Unmatched include.types / include.functions entries now abort with an error naming the entry, the config key, and how many types/enums/functions the crate actually exposes. Entries naming a declared [crates.opaque_types] type or an unsupported_public_items diagnostic still resolve, since both are legitimate include targets.
[Crates.include].types accepts the same qualified paths as [crates.exclude].types: include entries are now resolved through the shared type-identity matcher instead of being compared against the short name only.
One matcher decides whether a configured entry names a type: [crates.exclude].types demanded an exact rust_path, while [crates.exclude].fields and [[crates.source_crates]].roots also accepted the two-segment crate::Type shorthand — so exclude.types = ["c::Foo"] was a silent no-op for c::inner::Foo while exclude.fields = ["c::Foo.bar"] matched it. All three now share one rule. Exact-path entries still disambiguate two same-named types.
Unmatched [crates.exclude] entries are reported: exclude.types, exclude.functions, and exclude.methods entries that match nothing now warn, as exclude.fields already did. An exclusion is only observable through what it removes, so a typo'd entry previously excluded nothing and said nothing.
FieldResolver::accessor and FieldResolver::rust_unwrap_binding each carried a private copy of the virtual-namespace strip decision, gated on result_fields.contains(..) where the shared result_relative_path asks the broader is_valid_for_result(..). The copies could place the same fixture field somewhere the classifiers did not — the defect shape that emitted string(result.ActionResults) into a generated Go package. Both now call result_relative_path, so accessor emission, is_array, and the zig/brew/C serialized-path navigation share one definition of where a field's value lives.
An accessor whose virtual prefix hides a field the IR reaches but a hand-maintained result_fields omits now strips that prefix, instead of emitting a member access against the virtual label.
A result_fields entry the IR marks binding_excluded no longer strips its virtual namespace prefix in accessor emission. with_ir_fields already warns that such an entry is a config bug and no binding emits an accessor for the field, so neither spelling compiles; the accessor now agrees with is_array and the serialized-path generators rather than keeping a private answer.
alef adopt's create-once-seed warning no longer names the wrong command as the moment of loss. It said adopting a seed consents to alef "replacing its contents with a placeholder seed on the next generate", but write_scaffold_files_report's can_skip (!overwrite && !generated_header && exists && !is_alef_derived_output) runs before the ownership guard and consults no ownership signal, so a plain alef generate skips an adopted seed exactly as it skips an unadopted one. The replacement lands on the next write that passes overwrite: true -- an alef version scaffold regen, or alef all --clobber-create-once-seeds. An operator who tested the warning by running alef generate, saw the file untouched, and concluded the warning was false would have been reading accurate output; the loss was simply still days away. The flag help, the NOT ADOPTED -- create-once seeds stdout block, the per-path warn! and the seeds-only bail! now all name the overwriting regen and say a plain generate skips these paths.
Honor #[cfg_attr(alef, alef(skip))] on a function reached through a #[cfg(...)]-gated pub use re-export. apply_cfg_to_item cleared binding_excluded on every same-named function when applying a re-export's cfg gate, so a skipped function republished by a gated pub use was resurrected into the binding surface — while its siblings behind a plain (ungated) pub use kept their skip. When the resurrected function's signature could not be represented, the run aborted with a fatal lossy_sanitized_surface error naming an item the author had explicitly skipped, and the error's own suggested fix ("mark the item with #[cfg_attr(alef, alef(skip))]") was already applied. A cfg-gated re-export now contributes only the cfg gate; the declared skip wins, and the not(X) stub-pairing path clones only entries that were not excluded.
Fixed: the Kotlin Android build contract never produced the per-ABI native libraries its own release guard demands. alef build --release ran gradle assembleRelease, which fails validateJniLibsForRelease because src/main/jniLibs/<abi>/lib<crate>_jni.so was scaffolded as an empty directory and nothing ever filled it — the only native build in the contract, buildHostJni, produces a host-architecture library that can never satisfy an Android ABI directory. The generated build.gradle.kts now emits a buildAndroidJniLibs task that cross-compiles the JNI crate with cargo-ndk straight into src/main/jniLibs/, wired ahead of both the release guard and AGP's jniLibs merge.
Fixed: a [[crates.jni.target_dep_overrides]] entry naming a core-crate aggregate feature (the android-target shape) now satisfies the #[cfg(feature = "…")] gates of every member that aggregate enables. Feature satisfaction matched configured names literally, with only full hard-coded as a universal umbrella, so a shim gated on a member of the configured aggregate was emitted behind #[cfg(not(any(<target>)))] and silently disappeared from the cross-compiled Android artifact while every desktop target kept it.
Fixed: C snippets no longer wrap a batch input in an AlefHandle. An args entry typed json_object describes the fixture value, not the parameter's C shape, and c.rs's element_type backfill unwraps Vec<T> to T — so a Vec<ItemInput> batch parameter, which the FFI backend declares as const char *, had a handle built from its element type and passed where a pointer was expected (incompatible integer to pointer conversion passing '{PREFIX}AlefHandle' to parameter of type 'const char *'). Handle construction now consults the declared parameter through the same handle_param_type_name seam the omitted-optional sentinel uses: only a bare Named/Optional<Named> becomes a handle; Vec<_>, Map<_, _> and Json receive the serialized JSON string.
Fixed: a serde_json::Value (TypeRef::Json) parameter mapped to a json_object arg no longer aborts C e2e generation with "no resolvable type". named_type answers None for it, leaving element_type unset, which tripped the typed-handle panic whenever no options_type fallback was configured; it now renders as the JSON string the C ABI declares.
Fixed: the IR cache (.alef/<crate>/ir.json) was keyed on the Rust sources, the consumer crate's own version and the config, but never on alef itself, so upgrading alef did not invalidate it. A newer alef replayed an older alef's extracted ApiSurface verbatim and generated from it, and alef verify — which re-enters the same extraction — agreed. Because most ApiSurface fields are #[serde(default)], a field an older extractor never wrote came back as its default rather than as an error, so the replayed surface could be wrong rather than merely old. The alef build identity is now part of the key.
Fixed: a per-language cache hit only checked that each recorded output file still existed. A generated file edited by hand stayed a hit, the language was dropped from the generation set before anything read it, and alef generate reported Generated 0 files while leaving the edit in place; deleting .alef/ made the same command restore the file. A hit now also requires every recorded output carrying an alef:hash: stamp to still agree with that stamp — the same comparison alef verify makes. Outputs with no stamp (generated_header: false, create-once seeds) keep the existence-only rule.
Fixed alef all deleting a cache-hit language's still-valid binding output as a false orphan: the manifest-based route in sweep_manifest_orphans compared against a keep set (current_gen_paths) that was populated only from languages actually regenerated this run, so a language skipped via the per-language cache counted as having emitted nothing and its previously-generated files were swept. This produced an unbroken hit/miss/hit/miss cycle across repeated alef all runs, since the next run found a manifested output missing and regenerated, and the run after that deleted it again. Fixed by folding the already-correctly-seeded binding_ownership map into current_gen_paths before the sweep runs.
Closed the existence-only weakness in is_stage_cached (e2e, test-apps, scaffold, readme, docs stages): it now also requires every manifested output carrying an alef:hash: stamp to agree with that stamp under the run's inputs_hash, mirroring the fix already shipped for is_lang_cached. A consumer who hand-edits a generated e2e test, README, scaffold file, or docs page no longer gets a silent cache-hit skip. is_stage_cached now takes an inputs_hash: &str parameter; all 7 production call sites in src/bin_cli/ were updated to compute it from the same sources_hash/alef_toml_bytes used to stamp those outputs, hoisted ahead of the cache check where needed.
The four CHUNKS_RECIPE synthetic assertion handlers (chunks_have_content, chunks_have_embeddings, chunks_have_heading_context, first_chunk_starts_with_heading) hardcoded {result_var}.chunks and intercepted ahead of any field validation, across all seven wired backends (Rust, Python, TypeScript, Go, Java, C#, Elixir). A crate where chunks is declared on a nested IR type (e.g. Document) but not on the call's own root type (e.g. Envelope { results: Vec<Document> }) generated a result.chunks accessor that does not compile.
the anchored, whole-path oracle (FieldResolver::result_field_oracle_knows, already fixed for the derived-snippet path in the prior release) is now wired into the seven backends' per-call FieldResolver construction via with_ir_result_fields, and the four chunk synthetic handlers consult it before rendering: the assertion is now skipped with a not available on result type comment when the call's own declared root type positively does not declare chunks. An unresolved root or a name the oracle has no anchor for keeps the pre-existing permissive behaviour.
TypeScript and Go had no functions/CallIr threaded to the call site that builds this resolver; both now receive it (a new functions field on GoTestFunctionContext/GoTestFileContext, a new trailing parameter on render_test_file/render_test_case), so the WASM backend (which shares TypeScript's render_test_file) also gains the anchor for free.
alef e2e snippets-migrate rebases each existing_root-relative entry path onto the project root before matching a curated glob, so one declaration means the same thing to the migration comparison and to the coverage resolver.
alef e2e snippets-migrate no longer reports alef's own .alef-snippet-coverage.json as no_generated_equivalent; the coverage ledger is bookkeeping, not a snippet the project should hand-author.
excluded_default_features now also drops the name from the core dependency's explicit features = [...] line (not just the wrapper's own default = [...] array), reusing the shared core_dep_features_excluding helper used by swift/ruby/node/elixir/php/ffi (alef-tasks #331)
Handle Optional<Vec<Named>> on both sides of the trait bridge -- the inbound (extern "Swift") return path now decodes it per-element (Option<Vec<String>>), matching the already-tested param convention; the outbound (extern "Rust" Box-class) path now collapses it to one JSON blob (String?), matching the proven Optional<Vec<T>> DTO-getter limitation, since the two boundaries have different swift-bridge support for Option-wrapped containers (alef-tasks #333)
Downgrade a docs fixture's display: true to the debug formatter when the resolved Rust path is a struct/enum type from the crate's own IR, instead of emitting println!("{}", ...) against a type alef has no record of implementing Display (extraction discards impl Display via STD_TRAITS); warns naming the fixture and path so the mismatch is visible instead of a silent non-compiling snippet.
Stop telling readers to hand-add the provenance marker in the frozen-file report; point at alef adopt <path> --write instead, matching alef generate's own write-guard guidance.
Fixed: the generated Rust e2e crate declared tokio-stream only when a fixture needed a mock server, but the streaming collect recipe writes tokio_stream::StreamExt into every streaming test body. A streaming fixture with no mock server emitted a test file naming a crate the manifest did not depend on, failing the suite with error[E0433]: failed to resolve: use of unresolved module or unlinked crate tokio_stream. The dependency is now derived from the emitted test bodies themselves, so it cannot disagree with the recipe that put the path there. The mock-server codegen never named tokio_stream at all, so mock-server-only crates no longer carry the unused dependency.
Fixed: the same disagreement for serde_json in the generated Rust e2e crate. needs_serde_json was derived from the call's argument types (json_object / handle), which is blind to what an assertion emits: a contains over a collection field serializes each element through serde_json::to_value. A fixture with such an assertion, no JSON argument and no mock server emitted serde_json:: without the dependency.
Conftest.py now chdirs into test_documents_dir even when a fixture set also needs the mock server; the mock-server and file-fixture branches were an if/else, so a fixture needing both silently lost the chdir and file_path/bytes args resolved against pytest's invocation cwd instead of the configured test-documents directory.
The os import is now derived directly from the rendered test-file body (body.contains("os.")) instead of from a fixture-level heuristic ANDed with the body. The heuristic never checked a fixture's declared env.api_key_var, so a fixture with an api key var but no mock/client-factory/mock_url/bytes-path arg emitted os.Getenv(...) without importing "os".
A typed json_object argument's DTO constructor (ExtractInput.new(...)) is now namespaced under the crate's module (DemoCrawler::ExtractInput.new(...)), matching how the adapter mock_url request type is already qualified. The unqualified form resolved against top-level Ruby scope, not the generated binding's namespace.
A generated test now awaits a call whenever the core IR declares its target function/method async fn, not only when alef.toml's [call] async (or a per-language override) says so. CallConfig.r#async is a plain bool defaulting to false, so a fixture's config never distinguished "not async" from "never told me" -- a Rust function that became async fn after the fixture was authored left the config stale and the generated call unawaited. Added IrSignature.is_async (src/e2e/codegen/call_ir.rs) and ResolvedE2eCallRecipe::ir_is_async (src/e2e/codegen/recipe.rs), and wired TypeScript/WASM's _functions IR parameter (previously unused) through to render_test_case. An explicit per-language async override still wins verbatim over a disagreeing IR.
Fixed [crates.e2e.env] never reaching the local alef test --e2e run command's spawned process (only the pdfium library-path vars were injected); now each language's e2e command is prefixed with a plain export K='V'; for every configured [crates.e2e.env] entry, mirroring what test-apps run (registry mode) already did. This unblocks runtimes (e.g. Elixir's mix test, whose Rustler NIF loads during Mix's compile phase, before test_helper.exs runs) where setting the var from generated in-tree fixture code happens too late.
Fixed the napi/Node backend generating a struct field's #[napi(js_name = ...)] from the field's #[serde(rename = ...)] wire name instead of from casing policy, which made the compiled .node artifact expose a different property name (e.g. max_chars) than the one alef's own generated .d.ts declared for the same field (e.g. maxCharacters) — the two generators disagreed about the same IR input. js_name and the JSON wire rename are now derived independently, matching every other backend's separation of public identifier casing from serde_rename/serde_rename_all.
language_excludes's Language::Jni and Language::KotlinAndroid arms now fold [crates.jni].exclude_functions, so a function excluded only at the JNI level no longer shows as a false-positive gap in the KotlinAndroid docs page and snippet coverage ledger.
alef snippets check's deny_unclassified runner setting is now gated on the unified strict value (--strict flag OR [crates.docs.snippets].strict), not the raw --strict flag alone. Previously [crates.docs.snippets].strict = true left unclassified-side-effect snippets free to reach real execution at ValidationLevel::Run instead of being pre-emptively skipped.
alef snippets gaps no longer fails unconditionally on any finding. It now applies the same split check already used: missing include targets, missing required language variants, undocumented skips, and unknown fence languages always fail; an unreferenced-snippet-only finding fails only under --strict. Added GapReport::has_structural_gaps/GapReport::is_failure as the single shared source of that rule for both commands.
[crates.include] entries that match nothing now fail extraction instead of emptying the binding: include is an allowlist, so an entry naming no extracted item did not fail open — it dropped every type and enum from the surface while alef build still exited 0 and generated empty bindings. A typo (include.types = ["Kpet"]) or the qualified crate::path::Type spelling that [crates.exclude].types accepts both produced an empty type list silently. Unmatched include.types / include.functions entries now abort with an error naming the entry, the config key, and how many types/enums/functions the crate actually exposes. Entries naming a declared [crates.opaque_types] type or an unsupported_public_items diagnostic still resolve, since both are legitimate include targets.
[crates.include].types accepts the same qualified paths as [crates.exclude].types: include entries are now resolved through the shared type-identity matcher instead of being compared against the short name only.
One matcher decides whether a configured entry names a type: [crates.exclude].types demanded an exact rust_path, while [crates.exclude].fields and [[crates.source_crates]].roots also accepted the two-segment crate::Type shorthand — so exclude.types = ["c::Foo"] was a silent no-op for c::inner::Foo while exclude.fields = ["c::Foo.bar"] matched it. All three now share one rule. Exact-path entries still disambiguate two same-named types.
Unmatched [crates.exclude] entries are reported: exclude.types, exclude.functions, and exclude.methods entries that match nothing now warn, as exclude.fields already did. An exclusion is only observable through what it removes, so a typo'd entry previously excluded nothing and said nothing.
Fixed: a configured core-crate aggregate feature (the android-target / wasm-target shape, where the core crate declares bundle-target = ["gated", ...]) is now resolved through the core crate's own [features] table before it is used as an enabled-feature set. cfg_feature_satisfied matches feature = "X" leaves literally and treats only full as a universal umbrella, so every other aggregate name satisfied no gate at all: the Go, Java, C#, Kotlin, Zig, WASM and R backends silently dropped every #[cfg(feature = "<member>")] item from their surface even though cargo compiles it. The JNI shim gate was fixed earlier; this closes the same defect at every remaining site.
Fixed: the generated WASM crate's [features] default = [...] list is derived from the expanded configured feature set, so a configured aggregate now turns the binding crate's own passthrough rows on. Matching the literal aggregate against the cfg-referenced names produced default = [] — every passthrough declared and none enabled — which compiled the gated items out of the wasm artifact even after codegen kept them.
Fixed: language reference docs (and the README function list, which shares the same derivation) expand a configured aggregate the same way the backends do. effective_docs_features matched the aggregate literally, so a page documented strictly less API than the binding beside it.
Fixed: the JNI shim's default (non-overridden) target branch now counts the core crate's own default = [...] features. scaffold::languages::jni emits that branch's core dependency with no default-features = false, so those features are always active there; deriving the branch's enabled set from the configured features list alone dropped every shim behind a default-enabled gate.
Fixed: a [[crates.jni.target_dep_overrides]] entry with default_features = true now counts the core crate's declared default features for that target. Reading only the override's literal features list left the branch looking empty, so the shim was emitted behind #[cfg(not(any(<target>)))] — present on every other target and absent from the one the override exists to describe.
Expand a binding language's configured feature list through the core crate's [features] table in warn_on_ffi_feature_drift before differencing it against the FFI cdylib's effective set, matching the aggregate expansion backends::go/java/csharp/kotlin/zig/wasm now apply to their own enabled_features before with_cfg_filtered_deep. Previously the warning left the binding side unexpanded, so a fully FFI-covered aggregate produced a false "coverage gap" for every one of its members, and a real gap (an aggregate member the FFI side never reaches) went unreported because the literal, unexpanded set never matched the member's gate name.
Fold scaffold-only manifest writes (packages/java/pom.xml, crates/<name>-ffi/cmake/*.cmake, packages/python/pyproject.toml, and any other language's scaffold-managed manifest) into alef generate's changed_languages set, so a config-only edit (e.g. package_metadata.license) that rewrites a manifest with no corresponding bindings/service-api/public-api/stubs write no longer ships that manifest unformatted. Root cause: reconcile_managed_scaffold_manifests's write report fed any_written but never changed_languages, so on a regen where a language's bindings were cache-hit, that language's scaffold-only write fell outside format_scope and poly_paths never named its directory. alef all's full-tree convergence pass was never affected, since it reformats every byte under the repo root regardless of which phase wrote it -- which is why alef generate and alef all disagreed. Not Java-specific: reproduced identically for FFI's cmake config and Python's pyproject.toml on the same fixture; the fix is generic (languages_owning_changed_paths in src/cli/pipeline/format.rs), not a Java special-case.
alef snippets gaps/check's language-parity check now cross-references the e2e coverage ledger's expected set, so a function dropped for one language via exclude_functions (or any surface function_excluded_for_language folds in) no longer reports a false-positive missing-language-variant finding; a language the ledger genuinely expected but never generated still fails.
required_languages (config key and alef snippets gaps --required-languages) now accepts a session target name (kotlin_android, kotlin-android, node, wasm) as well as a fence tag, and names both accepted vocabularies when a value resolves to neither instead of silently dropping it from the comparison.
alef generate stamped scaffold, binding, public-API, stub, and service-API output with alef:hash: immediately after each write phase -- five finalize_hashes checkpoints, all ahead of the command's only format pass -- so poly (which refuses to format an already-stamped file) skipped every one of those files, shipping them in whatever shape the generator emitted. The five per-phase checkpoints are removed; current_gen_paths is now stamped exactly once, after formatting.
alef generate now strips a stamp an earlier (including pre-fix) run left on a file before its format pass runs (unstamp_before_formatting), so a tree that was previously stamped-and-never-formatted heals on the next alef generate instead of staying non-canonical forever.
alef generate now also formats when nothing changed this run but the on-disk tree is still non-canonical (generated_tree_needs_formatting), instead of gating purely on any_written/changed_languages; when that fallback fires, the format pass now covers every language this invocation resolved rather than silently no-op-ing on an empty changed_languages set.
Fixed: a snippet session's before hook is now run once per package instead of once per configured session target. kotlin and kotlin_android both resolve to Language::Kotlin, and typescript/node/wasm all resolve to Language::TypeScript, so several targets routinely describe one physical package and each carried its own copy of that package's hook. Every copy was executed, sequentially, before a single snippet could validate — and when the hook outran timeout_secs, the run paid that whole timeout once per target. Within an activation group a hook whose command and environment match one already attempted now replays that attempt's outcome; failures replay too, preserving the timeout classification every affected target is reported with.
Fixed: run_command no longer outlives the timeout it was given. The budget covered only the wait for the direct child; once that child exited, output collection waited for end of stream on pipes every descendant had inherited, so any process outliving the command — a Gradle daemon, an MSBuild node, an unwaited background job — held the call open indefinitely. A one-second budget was measured taking twenty seconds and still returning success. Output readers now buffer as bytes arrive and the drain gives up at a fixed grace, reporting everything the command actually wrote and tearing down the process group of anything still holding the pipes.
Fixed: SIGINT, SIGTERM and SIGHUP are now forwarded to every snippet subprocess group before alef exits. Snippet children are spawned into their own process group so a timeout can kill the whole tree, which also removed them from the terminal's foreground group: Ctrl-C reached alef and nothing else, so alef exited 130 while the entire hook tree — shell, build wrapper and build daemon — survived and reparented to PID 1, where a stale daemon goes on to poison the next run. A signal already ignored on entry stays ignored.
e2e: classify is_array by the path the accessor actually addresses.FieldResolver::is_array was a bare fields_array set lookup against the raw fixture spelling, while accessor — and result_relative_path, the answer the zig, brew and C generators share — strip a virtual namespace label first. A field spelled interaction.action_results therefore rendered as the slice result.ActionResults and classified as not-an-array, so Go's contains/contains_all/not_contains/contains_any renderers emitted string(result.ActionResults) instead of jsonString(...); converting a []T to string is not legal Go, so the generated package failed to build. is_array now routes its fallback through result_relative_path (which also applies alias resolution) rather than growing a second hand-rolled namespace strip beside is_optional's, keeping one definition of where a fixture field's value lives.
Fixed package_dir for java and kotlin (JVM, non-Android): a configured [crates.output].<lang> moved the generated source directory, but package_dir ignored config entirely and always answered the hard-coded literal packages/java / packages/kotlin, so mvn -f {dir}/pom.xml and cd {dir} && gradle targeted a directory the generator never wrote sources into whenever the configured tree moved outside packages/java / packages/kotlin. package_dir now derives the Maven/Gradle project root from the resolved output path the same way each backend's own generator disambiguates root-vs-source-dir shape.
Fixed jni_output_path (Kotlin JNI-mode emitter, used by kotlin_android): it joined a filename directly onto output_for("kotlin_android"), which names the Gradle project root, not a source directory, so JNI-mode .kt files were written straight into the project root instead of under src/main/kotlin/<pkg>/. Now delegates to the kotlin_android backend's own kotlin_source_dir, which already resolves that ambiguity for the backend's own file placement.
Fix alef build/alef generate running the umbrella gradle build (and gradle build -Prelease) for kotlin_android when no [workspace.build_commands.kotlin_android] overlay is declared, instead of the intended gradle assembleDebug/gradle assembleRelease. build_command_for's "gradle" arm matched on the shared bc.tool string, which cannot distinguish Kotlin from KotlinAndroid; it now asks a new shared build_defaults::gradle_build_task(Language, bool) helper, the same one default_build_config uses, so both derivations agree (#259). An explicit [workspace.build_commands.kotlin_android] overlay is unaffected and continues to win.
Fixed: the KotlinAndroid backend now expands aggregate feature names before cfg-filtering the API surface. [crates.kotlin_android].features = ["<aggregate>"] was matched literally against every #[cfg(feature = "<member>")] gate, so every item the aggregate enables was silently dropped from the Android binding even though cargo compiles it — API present on desktop, absent from the Android artifact, with no diagnostic. The list now goes through codegen::cfg::expand_configured_features, the same expansion the JNI shim target gate already uses.
Fixed: ResolvedCrateConfig::package_dir now honors a configured [crates.output].kotlin_android. It previously hardcoded packages/kotlin-android, so a consumer who configured that key had every cd {output_dir} && gradle build, lint, test, and clean command target a directory the backend never wrote to. It now delegates to the KotlinAndroid backend's own ProjectLayout, which already distinguishes a configured Gradle project root from a configured Kotlin source directory, and reads the backend's DEFAULT_AAR_ROOT constant for the unconfigured case instead of repeating the literal.
Fixed: alef generate wrote the unconfigured Kotlin/Android Gradle project to packages/kotlin_android while alef build, lint, test, clean, setup, the scaffolded .gitattributes and sync_versions all targeted packages/kotlin-android. The output template's packages/{lang} default was spelled from the config key; it now resolves to the backend's own DEFAULT_AAR_ROOT, so both halves name the same directory. Only projects with no [crates.output].kotlin_android entry are affected.
Fixed: the build.gradle.kts snapshot for the Kotlin/Android backend did not record the buildAndroidJniLibs cross-compilation task, leaving snapshot_basic red.
Escape map-key literals in optional_renderers.rs for rust, java, kotlin, kotlin_android, csharp, zig, dart, php, r, and c so a key containing " or \ can no longer break out of its target-language string literal and emit code that fails to parse (#297).
Filter a json_object argument's object literal against the bound type's declared fields before emitting it, so the typed-const snippet path (excess-property-checked by tsc) and the as-cast e2e test path (not checked) can no longer disagree about the same fixture; an undeclared key now panics generation naming the fixture, type, and key instead of silently compiling in one path and failing TS2353 in the other (#322).
Fixed a quoted string map key in an e2e field path (labels["theme"]) being quoted twice. parse_path kept the surrounding quotes on the key and every renderer added its own, so Swift/Go/Java/Kotlin/Ruby/PHP/C#/R/C/Dart/Zig/Rust emitted the unparseable labels[""theme""] and TypeScript/wasm emitted labels["\"theme\""] — syntactically valid TypeScript that silently looked up a key no map holds. Quotes are now stripped once in parse_path (the only place that parses brackets); the renderers stay the sole owners of quoting. Single-quoted keys (labels['theme']) are handled the same way, and a quoted digit key (labels["0"]) now indexes identically to labels[0].
Fixed map keys being interpolated into generated string literals without escaping in the shared field-access renderers. A key containing " or \ closed its literal early and emitted code that did not parse; the shared renderers now emit keys through a single escaping helper for TypeScript, Node, wasm, Go, Ruby, Elixir, Python, Gleam and Swift.
Fixed: alef e2e snippets-migrate now honors [crates.e2e.snippets].curated_snippets. The command called the curated-unaware migration::compare_root, so every hand-authored file reported as no_generated_equivalent regardless of configuration and a consumer could not tell a declared, intentional curated snippet from a genuine coverage gap. The comparison and its report rendering moved to bin_cli::snippet_migration, which routes through compare_root_curated.
Fixed: the snippets-migrate text report gives a declared curated file its own curated label instead of no_generated_equivalent, matching the curated flag the --json report already carried per entry.
Fixed: migration::compare_root keyed existing files against existing_root and generated files against the configured output, so when output is a subdirectory of the migrated tree (alef e2e snippets-migrate docs/snippets against output = "docs/snippets/generated") the two key spaces were disjoint by construction and every file alef itself had generated reported as no_generated_equivalent. One consumer saw 7796 false positives against 3 genuinely hand-authored files. The generated keys now carry the nested prefix so both sides key off existing_root; parallel roots are unchanged.
Fixed a gap in the TypeScript e2e generator's undeclared-field filter: ts_builder_expression_inner's declared-key guard only covered its own object literal and its recursive calls, not the SEPARATE node_value_expression path used to build nested struct-field object literals (an already-typed field's inner object value). A fixture with an undeclared key nested one level deeper than the top-level json_object argument silently reached both the docs snippet and the generated vitest test, reproducing the same snippet-vs-e2e asymmetry (excess-property-checked const binding vs unchecked as cast) the top-level filter (#322) was meant to close.
Fixed: a documentation snippet no longer derives an accessor for a field the result type does not declare at any depth. The anchored availability oracle judged only a field path's first segment, so any path whose root was a real field was waved through whatever it named afterwards — one consumer shipped 28 non-compiling snippets reading a name that exists only as an alef.toml key (Property 'documentStructure' does not exist on type 'ExtractedDocument'). The oracle now walks the whole path through the IR struct graph, and abstains (deriving the accessor as before) whenever the walk leaves the graph into a map value, a serde_json::Value, a primitive, or a type outside the extracted surface.
Fixed: C documentation snippets for trait-bridge registry operations now call the symbol the FFI backend exports. The derived ABI identity ({prefix}_clear_{trait_snake} / {prefix}_unregister_{trait_snake}) was applied only when no function name was configured at all, so a well-formed [crates.e2e.calls.*] function shadowed it — snippets called the plural clear_fn config text against a header that declares the singular trait-derived symbol, and lost the trailing out_error out-param with it. Only an explicit [crates.e2e.calls.*.overrides.c] function now outranks the derivation.
Fixed: C# trait-bridge test stubs no longer carry an explicit private modifier. A docs snippet emits the stub at file scope after top-level statements, where private is CS1527; omitting the modifier is legal both there (defaults to internal) and nested in the e2e test class (defaults to private), matching the spelling build_csharp_visitor already uses.
Fixed: Dart trait-bridge snippets now import dart:typed_data for every typed-list class the Dart mapper can emit, not just Uint8List. A stub whose methods take or return Vec<f64>/Vec<i64> spelled Float64List/Int64List with the library never imported. Both the stub emitter and the snippet preamble now ask backends::dart::type_map which names come from that library.
Fixed: alef --version no longer reports tree: DIRTY for every binary installed with cargo install --git. Cargo drops a .cargo-ok completion marker into each checkout it creates, and the build stamp classified the working tree with git status --porcelain, which counts untracked files. Every git-installed binary therefore printed the "not reproducible from commit" warning, and a warning that fires on every install is one nobody reads — which is how genuinely dirty output ends up attributed to a commit it cannot be reproduced from.
Stop reporting deliberately #[alef::skip]/#[doc(hidden)]-excluded functions and methods as missing snippet coverage — the snippet driver now checks the IR's binding_excluded flag before adding a fixture/language cell to expected, matching the carve-out the API-reference docs generator already applies for Rust's own page (Language::Rust is never excluded).
A struct returned only through Option<T>/Vec<T>/a Map value (never returned bare) was wrongly classified as an input-only type, so options.py emitted it as a public dataclass while the generated function actually returned the native pyclass; is_return_type is now set from any function return type that references the struct at any depth, not only a bare Named return.
An AsyncMethod adapter whose declared return type is a public options dataclass now converts the engine's native return value with the existing _from_native_<snake> converter before handing it back, instead of returning the native pyclass under a dataclass-typed annotation.
An adapter param typed as a public options dataclass (on both AsyncMethod and the general Streaming param path) is now converted to the native pyclass with a _to_rust_<snake> converter before the engine call, instead of forwarding the dataclass instance directly.
Fixed the Python api.py streaming wrapper yielding a type it does not declare. A streaming adapter whose item type is emitted as a public options dataclass annotated its return as AsyncIterator[options.Item] but re-yielded the native _internal_bindings pyclass, which pyrefly rejects with invalid-yield. The wrapper body now applies the options._from_native_<snake> converter alef already generates for that type, and imports it alongside the item type. Items with a single native identity (return types, opaque types) keep yielding the item unchanged.
Fixed generated Rust documentation snippets for streaming fixtures failing to compile with error[E0433]: cannot find module or crate 'tokio_stream'. The streaming recipe drains a stream through tokio_stream::StreamExt, but nothing added the matching crate:tokio-stream requirement, so the snippet check project's [dependencies] never declared it. The three body-derived Rust crate requirements (serde_json, tokio, tokio-stream) now come from one table, and tokio-stream is pinned alongside them in the Rust snippet validator.
Fixed generated Rust documentation snippets for streaming fixtures referencing a result binding their body never creates (error[E0425]). A streaming call binds stream and drains it into chunks; the snippet's tail was still rendered against the non-streaming result variable, both for field accessors and for the match an error fixture renders. A streaming snippet now presents the collection it actually binds, and an error-expecting streaming call is unwrapped before its stream is drained.
Fixed: a docs snippet no longer drops a declared result field whose name collides with a legacy streaming pseudo-field name (chunks, chunks.length, stream_content, tool_calls, finish_reason, ...). The snippet show-derivation rejected those names unconditionally, while every e2e assertion renderer gates the same name list on resolve_is_streaming — so a non-streaming call whose result type genuinely declares such a field kept its e2e assertions but silently lost its snippet accessor. Both generators now ask resolve_is_streaming once and agree.
alef snippets check no longer skips its gap pass silently. With neither docs_dirs nor required_languages configured under [crates.docs.snippets] the pass is still skipped, but the unset keys are now warned about by name, and under strict the skipped pass fails the run instead of reporting no failure.
alef all now formats every file it emits. poly refuses to touch a file whose leading lines carry an alef:hash: line — under --fix and under --check alike — and alef all stamped each write phase's output immediately after writing it, ten finalize_hashes checkpoints all ahead of its single format pass. Every one of those stamps made the format pass a no-op for the files it covered, so binding, stub, public-API and scaffold output shipped in whatever shape the generator emitted. Measured on a neutral eight-language fixture: 21 of the 93 emitted files were files poly fmt would have rewritten — C# sources, packages/java/pom.xml, packages/python/pyproject.toml and api.py, Ruby, PHP, the node .d.ts, and the FFI cmake config.
alef all also repairs output an earlier alef stamped without formatting. Such a file's generated body has not changed, so no writer rewrites it, so it keeps its stamp and stays invisible to every formatter forever; reordering alone reaches only the files that happen to change in the same run (measured: 6 of 21). The format pass now strips the alef:hash: line from the paths it is about to re-stamp, and a read-only poly fmt --check --fix-generated probe makes a run whose writers changed nothing still notice a non-canonical tree.
Fixed swift_shim_return_marshal (Swift FunctionParam trait-box FFI shim) wrapping an enum-typed trait method return directly in RustString(...), which requires a String argument and does not compile against an enum value; the shim now JSON-encodes the enum via JSONEncoder before wrapping it, decided by consulting ApiSurface::enums rather than the TypeRef::Named discriminant. Struct-typed (JSON) Named returns are unchanged.
Fixed the Swift trait-bridge default method stub (gen_single_trait_bridge_file's default_body) emitting return "{}" for a has_default_impl method with a non-excluded enum-typed return, which does not type-check against the enum's own declared Swift return type; it now constructs a real case of the enum (via a new unit_enum_default_case IR lookup) when one is available, falling back to the prior placeholder body otherwise.
swift: derive the Rust bridge crate's literal Cargo feature-list widening from cfg alternatives (#[cfg(any(feature = "a", feature = "b"))]) discovered in the parsed API surface instead of a hard-coded pair of feature names, so any source crate that gates a capability behind two sibling features benefits, not just one whose features happened to be named a particular way.
Fixed: packages/swift/Sources/RustBridgeC/RustBridgeC.h no longer changes shape between otherwise identical runs. Both writers of the umbrella header (the Swift scaffold and the MaterializeSwiftBridge post-build step) now compare a freshly assembled header against the committed one as C rather than as text, and keep the committed bytes when the declarations are unchanged. Previously the raw swift-bridge concatenation overwrote the formatted committed header on every run; because poly fmt skips any file carrying an alef:hash: line, whether the shipped header ended up formatted or raw depended on whether the format pass happened to see it before it was stamped — the same inputs produced two different files, hundreds of lines apart, with alef exiting 0 either way.
Fixed: a partial swift-bridge build output can no longer produce a plausible-looking but uncompilable RustBridgeC.h. The assembled header is now checked for completeness — every RustStr / __private__* type its declarations reference must also be defined in it — instead of being trusted because both input files existed. A partial assembly keeps the committed populated header and warns; with nothing populated to fall back on, alef now fails with a message naming the undefined types and the build directory the inputs came from, rather than writing the degraded header and exiting 0.
Fixed the Swift inbound trait bridge so Vec<Named> return values and params convert per-element instead of falling through as a raw Vec<String>/Vec<Named> type mismatch (alef-tasks #308).
Fixed inbound_bridge_type's Map arm to declare a single JSON String blob for the extern "Swift" boundary instead of a typed HashMap<K, V> that swift-bridge cannot parse (alef-tasks #309).
Fixed the outbound Swift trait-bridge protocol (swift_type_name) to declare Map as a single JSON String blob instead of [K: V], which previously double-encoded Map<_, Named> values (alef-tasks #309).
Fixed the outbound box shim's return marshal catch-all to wrap the bridge call in RustString(...) whenever the shim's FFI return type is RustString, fixing a compile break for Map returns (and any other catch-all shape) that the Map fix above exposed.
Fixed Swift documentation snippets subscripting, indexing, or iterating a field swift-bridge JSON-bridges to a single RustString. The snippet generator emitted labels()["theme"] for the very field the e2e file generated beside it from the same IR declared unspellable, so the snippet could not compile. A show operation whose path steps past a JSON-bridged leaf is now clamped to that leaf — the case the e2e derivation explicitly blesses, so the reader still sees the field — and an iterate over such a leaf is dropped.
Fixed (swift): FunctionParam trait-bridge protocols and Swift{Trait}Box shims are emitted into Sources/RustBridge/, but excluded_named_type_bridge_policy exempted has_default_impl methods from the JSON-string boundary policy. A defaulted method's Named types therefore kept their real Swift names in the emitted signature, which cannot resolve in RustBridge — the package's target graph runs <Module> -> RustBridge, so public DTOs live downstream. Every Named type a bridged trait mentions now crosses as a JSON String, defaulted or not, matching the contract gen_rust_crate::plugin_inbound already documented and restoring the invariant intended by "exclude all Named types in trait bridges". This is the root cause of #258; it affected parameter position as well as return position.
Fixed (swift): reverted the 0.67.5 change that JSON-encoded an enum-typed trait-method return before wrapping it in RustString. JSONEncoder().encode(...) required the DTO to be nameable and Encodable in RustBridge, where it is neither, so it traded one compile error for another; with the boundary policy corrected the bridge call already yields a JSON String and re-encoding would double-encode the payload.
resolve() on an alef.toml with zero [[crates]] entries now returns ResolveError::NoCratesConfigured instead of Ok(vec![]), so alef go-tag, alef validate versions --exit-code, and alef publish validate can no longer silently process zero crates and exit 0.
alef check-registry --registry github-release now warns when it verified only that the release exists (no --asset-prefix or --required-assets given), so a CI variable that expanded to nothing is no longer indistinguishable from "all N artifacts are attached"; --registry zig/--registry swift are unaffected since they intentionally check existence only.
alef e2e validate now applies the same [e2e].languages fallback (to the crate's scaffolded languages) that alef e2e generate --snippets-migrate and alef test-apps run already applied, so an unset [e2e].languages no longer silently disables the "0 test functions" and "unsupported language" checks. The success message also now distinguishes "no fixtures found" from "N fixtures validated".
alef lint now fails when poly is not on PATH instead of warning and reporting a clean run; poly is the entire implementation of alef lint, so there was no partial coverage to report.
alef release-metadata --targets now rejects a CSV that trims to non-empty but splits into zero real target tokens (e.g. ,,, or a lone ,), which used to resolve identically to the deliberate --targets none (release_any: false, exit 0, no diagnostic).
Fixed Swift trait-box shims for a Vec<Named> in return or parameter position. A bridged Named crosses as a JSON String, so the bridge protocol declares [String] and inbound_bridge_type declares Vec<String>, but swift_shim_return_ffi_type fell through to RustString and swift_shim_return_marshal returned the [String] unchanged — cannot convert return expression of type '[String]' to return type 'RustString'. On the parameter side the decode called .toString() on a RustVec<RustString>, which has no such member. Both now use the element-wise RustVec conversion Vec<String> already used.
Fixed a Swift trait-box shim returning Option<Named>, which handed the bridge's String? back where the shim declares RustString. nil is now sent as the JSON literal null, which is what the Rust wrapper's serde_json::from_str::<Option<T>> expects.
Fixed non-throwing ()-returning Swift trait-box shims never calling the conformer. The marshaller emitted only return () and discarded the bridge call, so every such method was a silently compiling no-op.
alef verify no longer reports create-once scaffold seeds as frozen generated files. FrozenFile means "alef would write this path and the write guard refuses it forever", and for a create-once seed the antecedent is false: alef emits the path only when it is absent, so on an existing file there is no write to refuse and nothing is lost by the missing marker. Verify nonetheless listed them under a "Frozen create-once seeds detected" heading whose only remedy was alef adopt <path> --write --clobber-create-once-seeds -- a flag alef's own output labels DANGEROUS -- for files this project's documentation calls user-owned after scaffold (generated_header: false). Measured in a consumer repo: alef adopt --converged-only adopted 0 of 102 reported paths, 72 of them refused by alef itself as seeds, including 13 LICENSE files, e2e/java/mvnw, kotlin-android/gradlew, build.zig.zon and several .gitkeeps. Recording ownership instead was considered and rejected: it proves nothing further (alef still never rewrites the body, and the stamp covers generation inputs rather than the seed's hand-grown contents) while handing the write guard the licence --clobber-create-once-seeds exists to gate. The count is not dropped -- it is stated in the new coverage report on every run, including a clean one, whereas the old heading was printed only when some other check had already failed. Extends the 0.67.3 stale-seed fix rather than duplicating it: that one taught the stamping pass to ask the question verify asks (stampable_output_paths); this one removes the finding verify had no answer for.
A stamped .clang-format was written and then never read back by anything. The ownership walk's scan set is documented to be a superset of everything the emit table can stamp, and it had drifted: .clang-format is scaffolded generated_header: true for every FFI target and is stamped on write (it is YAML, so # line comments apply), but a dotfile with a single leading dot reports Path::extension() == None and the name was never added to VERIFY_SCAN_FILENAMES. The walk filters on name and extension before reading any content, so the file was not merely unverified but unverifiABLE. The scan predicate now asks the emit side's own is_markable_path directly, so anything alef stamps on write is opened on read and the two sides cannot drift apart again by hand. Widening the scan set only causes more files to be read; a file is still reported only when it carries a marker, so this adds no false positives.
Fixed: a documentation snippet now passes every argument the target binding declares required. The snippet generator read a fixture's optional argument flag as the arity of both JavaScript bindings, but only NAPI widens a parameter whose type derives Default to settings?: in its .d.ts; wasm-bindgen emits the parameter from the Rust signature alone. A wasm snippet therefore ended its argument list where only node may, which tsc rejects with TS2554: Expected 2 arguments, but got 1. Arity is now decided per target through ParamOptionalityRule, whose node arm calls the NAPI backend's own param_is_optional so the snippet and the declaration it compiles against cannot drift.
Fixed: a generated loop binding no longer takes the name of the collection it iterates. A fixture may author iterate with the same name the call's result is bound to, which Rust, Python and Go accept but TypeScript rejects (for (const result of result.results) is TS2448/TS7022, not a shadow) and Java rejects as a redeclaration. The name is now decided once, before any accessor is rendered, and only when it actually collides.
Fixed: a WASM documentation snippet that names a type [crates.wasm] exclude_types keeps out of the binding is refused instead of published. The snippet's imports come from the crate IR, which is not the package's export list, so the snippet imported a symbol that does not exist. This is the type-side twin of the existing refusal for a function the WASM target does not export, and it records a coverage gap the same way.
Fixed: wasm docs snippets now optional-chain an access through an Option<T> field, matching the node snippet for the same fixture. FieldResolver::accessor dispatched typescript/node to the optionality-aware TypeScript renderer and let wasm fall through to a second renderer that knew nothing about optionality, so one fixture produced result.document?.nodes for node and the TS18048result.document.nodes for wasm — every wasm structure snippet on an optional field failed to type-check.
Fixed: configured_swift_features's cfg-alternative widening (alef-task #306) fed a companion feature straight into the core dependency's features = [...] line without checking [crates.swift].excluded_default_features, bypassing the exclusion entirely and reactivating an opt-in native/pkg-config-only feature that was never in the core crate's own default set. Confirmed as a net-new regression (not already-latent Cargo unification) via a synthetic fixture workspace measured with cargo tree -e features. excluded_default_features now blocks widening a companion into that line, not just into the wrapper's own default = [...] array.
Fix a real flake in src/bin_cli/core_commands/post_build_format_order_tests.rs (all_formats_scaffold_output_before_stamping_it, all_reformats_a_scaffold_file_left_stamped_and_uncanonical_by_an_earlier_run), which failed under parallel cargo test --lib load with Blocking waiting for file lock on package cache and passed in isolation/on re-run. Root cause: these tests' run_all helper drives real alef all, whose full-regen converge_full_regen residuals (cargo fmt --all, cargo sort -n -w) spawn genuinely real cargo subprocesses whenever the fixture has a root Cargo.toml -- unlike ALEF_SKIP_COMMANDS/SkipCommandsGuard, which only gates PostBuildStep::RunCommand, not these residual passes. crate::test_support::CWD_LOCK did not close this: four tests in src/cli/pipeline/format/tests.rs (run_workspace_cargo_sort_sorts_every_member_regardless_of_language, run_cargo_fmt_formats_workspace_rust_files_when_available, converge_full_regen_formatting_leaves_workspace_sorted_and_poly_fmt_check_clean, format_generated_full_regen_routes_through_convergence_loop) call the real-cargo-spawning functions directly with an explicit base argument and never touch the process cwd, so they never take CWD_LOCK and can run concurrently with alef all's own cargo invocations on cargo's shared machine-wide package-cache lock. Added a new shared REAL_CARGO_LOCK / RealCargoGuard to src/test_support.rs, mirroring the existing CWD_LOCK/SKIP_COMMANDS_LOCK pattern, and wired it into all five affected tests. No assertions were weakened; both flaky tests still assert the same stamp-before-format defect they were written to guard.
Hardened a regression test's poly fmt --check . assertion (post_build_format_order_tests.rs) with --fix-generated: without the flag the assertion was vacuous for the post-build-owned header it targets, since poly skips any hash-stamped file under a plain --check regardless of its body.
Fix frb_version_check.rs's test module failing to compile on Windows (std::os::unix::fs::PermissionsExt, Permissions::from_mode) by gating the four unix-only items (fake_codegen_binary and the two tests that use it, plus the now-unix-only imports) behind #[cfg(unix)], while keeping the four platform-neutral tests running unconditionally on every OS.
Rebuild ESCAPING_LANGUAGES in map_key_quoting_tests.rs to drive per-language assertions directly (was a decorative length check only) and extend it to all 19 e2e target languages.
Corrected a false claim in post_build_format_order_tests.rs, which asserted in prose that alef all "does not have this defect". It did, for every phase. The alef all scaffold phase now has executable coverage instead: one test that a scaffold-phase file the formatter would change ships canonical, and one that a file left stamped-and-uncanonical by an earlier run is repaired. Both open with an anti-vacuity control that fails loudly if the generator's own output ever becomes canonical.
Fixed the RED swift e2e integration test count_min_on_optional_vec_of_named_uses_native_optional_count, which shipped red through 0.67.3 and 0.67.4.
The test's premise was stale, not the codegen: it asserted an opaque parent's Option<Vec<Named(struct)>> field bridges natively as Optional<RustVec<T>>, but field_needs_json_bridge in src/backends/swift/gen_rust_crate/type_bridge.rs has no is_opaque dependence — it returns true unconditionally for any optional Vec<_> field, and both wrappers::getters::emit_getters and extern_block::emit_extern_block_for_type check it before parent_first_class is ever consulted.
Confirmed by rendering the exact fixture: the field emits result.elements().toString().count, never elements()?.count ?? 0, regardless of the parent's opacity.
Split the single test into two paired tests in tests/e2e_swift_residual_e2e_codegen_bugs.rs — opaque parent and first-class parent — both asserting the JSON-bridged .toString().count shape, so a fix that corrects one parent shape while silently regressing the other is caught by whichever arm it breaks.
No production code changed; src/e2e/codegen/swift/values.rs and src/backends/swift/** are untouched.
Restored the Map<_, Named> probe in the two-target SwiftPM trait-box compile gate (trait_box_swiftpm_compile.rs), which had been dropped.
Removed
Removed bad-return and bad-argument-type from the default [[tool.pyrefly.sub-config]] suppression list scaffolded for **/api.py — both were masking the boundary-mismatch bugs above. bad-argument-count, not-iterable, and missing-attribute remain suppressed; they were not diagnosed as part of this fix. Existing consumers keep their already-scaffolded pyproject.toml (scaffold output is user-owned after first write) until they regenerate or manually adopt the new default.