alef 0.81.0

Opinionated polyglot binding generator for Rust libraries
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
//! Unit tests for [`super`]'s cache key, manifest and committed-record logic.
//!
//! Split out of `cache.rs`, which is over the 1,000-line cap and may not grow. Mirrors the
//! `ownership.rs` / `ownership/tests.rs` split already used in this directory.

use super::*;
use crate::cli::cache_identity::key_for_test as test_key;

fn api_with_ordered_entries(entries: &[(&str, &str)]) -> crate::core::ir::ApiSurface {
    let mut api = crate::core::ir::ApiSurface {
        crate_name: "sample_crate".to_string(),
        ..Default::default()
    };
    for (name, path) in entries {
        api.excluded_type_paths.insert((*name).to_string(), (*path).to_string());
        api.excluded_trait_names.insert((*name).to_string());
    }
    api
}

#[test]
fn validate_cache_crate_name_accepts_normal_names() {
    validate_cache_crate_name("my-lib").unwrap();
    validate_cache_crate_name("sample_crate").unwrap();
    validate_cache_crate_name("sample_markdown").unwrap();
}

#[test]
fn validate_cache_crate_name_rejects_path_separators() {
    assert!(validate_cache_crate_name("../escape").is_err());
    assert!(validate_cache_crate_name("foo/bar").is_err());
    assert!(validate_cache_crate_name("foo\\bar").is_err());
}

#[test]
fn validate_cache_crate_name_rejects_dot_aliases() {
    assert!(validate_cache_crate_name("..").is_err());
    assert!(validate_cache_crate_name(".").is_err());
}

#[test]
fn validate_cache_crate_name_rejects_nul_byte() {
    assert!(validate_cache_crate_name("foo\0bar").is_err());
}

#[test]
fn ir_cache_dir_scopes_by_crate_name() {
    assert_eq!(ir_cache_dir("crate-a"), Path::new(CACHE_DIR).join("crate-a"));
    assert_eq!(ir_cache_dir("crate-b"), Path::new(CACHE_DIR).join("crate-b"));
    assert_ne!(ir_cache_dir("crate-a"), ir_cache_dir("crate-b"));
}

#[test]
fn repeated_ir_serialization_preserves_cache_and_provenance_hashes() {
    let first = api_with_ordered_entries(&[
        ("Gamma", "sample_crate::gamma::Gamma"),
        ("Alpha", "sample_crate::alpha::Alpha"),
        ("Beta", "sample_crate::beta::Beta"),
    ]);
    let second = api_with_ordered_entries(&[
        ("Beta", "sample_crate::beta::Beta"),
        ("Gamma", "sample_crate::gamma::Gamma"),
        ("Alpha", "sample_crate::alpha::Alpha"),
    ]);

    let first_json = serde_json::to_string_pretty(&first).expect("serialize first IR");
    let second_json = serde_json::to_string_pretty(&second).expect("serialize second IR");
    let generated = "// auto-generated by alef\npub fn sample() {}\n";
    let first_cache_hash = compute_lang_hash(&first_json, "sample", "[sample]\n");
    let second_cache_hash = compute_lang_hash(&second_json, "sample", "[sample]\n");
    let first_file_hash = crate::core::hash::compute_file_hash(generated);
    let second_file_hash = crate::core::hash::compute_file_hash(generated);

    assert_eq!(first_json, second_json);
    assert_eq!(first_cache_hash, second_cache_hash);
    assert_eq!(first_file_hash, second_file_hash);
    assert_eq!(
        crate::core::hash::inject_hash_line(generated, &first_file_hash),
        crate::core::hash::inject_hash_line(generated, &second_file_hash)
    );
}

#[test]
fn manifest_is_sorted_deduplicated_and_newline_terminated() {
    let directory = tempfile::tempdir().expect("tempdir");
    let manifest = directory.path().join("rust.manifest");
    let alpha = directory.path().join("alpha.rs");
    let beta = directory.path().join("beta.rs");

    write_manifest(&manifest, &[beta.clone(), alpha.clone(), beta.clone()]).expect("write manifest");

    let content = std::fs::read_to_string(manifest).expect("read manifest");
    assert_eq!(content, format!("{}\n{}\n", alpha.display(), beta.display()));
}

#[test]
fn empty_manifest_is_not_a_cache_hit() {
    let directory = tempfile::tempdir().expect("tempdir");
    let manifest = directory.path().join("rust.manifest");
    std::fs::write(&manifest, "").expect("write empty manifest");

    assert!(!outputs_exist(&manifest));
}

/// A manifest alef cannot read is no evidence that the outputs it lists are on disk, in either
/// of the two ways it can fail to read: absent (what an interrupted `write_lang_hash` leaves,
/// since the hash and the manifest are two separate writes) or present-but-unreadable.
///
/// Asserted on `is_lang_cached` -- the decision a caller acts on by skipping generation
/// entirely -- and not on a diagnostic. The defect this pins was a `true` returned while every
/// message about the cache was already saying the right thing, so a test that watched the
/// messages would have passed throughout. ~keep
#[test]
fn unreadable_output_manifest_is_a_cache_miss() {
    let tmp = tempfile::tempdir().expect("tempdir");
    let _cwd = crate::test_support::CwdGuard::enter(tmp.path());

    let generated = tmp.path().join("bindings.py");
    std::fs::write(&generated, "# generated\n").expect("write generated output");
    write_lang_hash("sample-crate", "python", &test_key("hash-1"), &[generated]).expect("write hash and manifest");
    assert!(
        is_lang_cached("sample-crate", "python", &test_key("hash-1")),
        "a matching hash whose manifested outputs are all present must be a hit"
    );

    let manifest = hashes_dir("sample-crate").join("python.manifest");
    std::fs::remove_file(&manifest).expect("remove the manifest, leaving the hash behind");
    assert!(
        !is_lang_cached("sample-crate", "python", &test_key("hash-1")),
        "a hash with no manifest at all must not validate a cache hit"
    );

    std::fs::create_dir_all(&manifest).expect("put something unreadable where the manifest belongs");
    assert!(
        !is_lang_cached("sample-crate", "python", &test_key("hash-1")),
        "a manifest that exists but cannot be read must not validate a cache hit either"
    );
}

/// Table-driven coverage of `is_stage_cached` -- the single choke point every generation
/// stage (scaffold, readme, docs, e2e, test-apps) calls to decide "up to date" versus
/// "regenerate". A hit requires both a matching input hash and every manifested output path
/// still present on disk; either failing alone is a miss.
///
/// The empty-recorded-paths row is deliberately a miss, not a hit. `outputs_exist` (shared by
/// `is_stage_cached` and `is_lang_cached`) treats zero recorded paths as zero evidence of
/// surviving output, so a stage that recorded nothing never satisfies the cache. Treating it
/// as a hit instead would mean a stage that fails to record its own outputs -- necessarily a
/// bug, since every writer here calls `write_stage_hash` with the paths it just wrote --
/// silently disables output verification for that stage forever, which is the deleted-output
/// bug this test file exists to catch, just triggered a different way. ~keep
#[test]
fn a_deleted_recorded_output_downgrades_a_cache_hit_to_a_miss() {
    struct Scenario {
        name: &'static str,
        stage: &'static str,
        recorded_outputs: &'static [&'static str],
        delete_output: Option<&'static str>,
        query_hash: &'static str,
        expect_hit: bool,
    }

    let scenarios = [
        Scenario {
            name: "matching hash with every recorded output present is a hit",
            stage: "hit",
            recorded_outputs: &["a.rs", "b.rs"],
            delete_output: None,
            query_hash: "hash-1",
            expect_hit: true,
        },
        Scenario {
            name: "matching hash with one recorded output deleted is a miss",
            stage: "deleted-output",
            recorded_outputs: &["a.rs", "b.rs"],
            delete_output: Some("b.rs"),
            query_hash: "hash-1",
            expect_hit: false,
        },
        Scenario {
            name: "non-matching input hash is a miss regardless of outputs",
            stage: "stale-hash",
            recorded_outputs: &["a.rs"],
            delete_output: None,
            query_hash: "hash-2",
            expect_hit: false,
        },
        Scenario {
            name: "empty recorded paths is a miss, not an automatic hit",
            stage: "empty",
            recorded_outputs: &[],
            delete_output: None,
            query_hash: "hash-1",
            expect_hit: false,
        },
    ];

    for scenario in scenarios {
        let tmp = tempfile::tempdir().expect("tempdir");
        let _cwd = crate::test_support::CwdGuard::enter(tmp.path());

        let outputs: Vec<PathBuf> = scenario
            .recorded_outputs
            .iter()
            .map(|name| {
                let path = tmp.path().join(name);
                std::fs::write(&path, "// generated\n").expect("write generated output");
                path
            })
            .collect();
        write_stage_hash("sample-crate", scenario.stage, test_key("hash-1").as_str(), &outputs)
            .expect("write stage hash and manifest");

        if let Some(to_delete) = scenario.delete_output {
            std::fs::remove_file(tmp.path().join(to_delete)).expect("delete recorded output");
        }

        assert_eq!(
            is_stage_cached("sample-crate", scenario.stage, &test_key(scenario.query_hash)),
            scenario.expect_hit,
            "scenario `{}` expected hit={}",
            scenario.name,
            scenario.expect_hit
        );
    }
}

/// `write_scaffold_manifest` must round-trip through `read_scaffold_manifest`,
/// sorted and deduplicated like every other manifest. This is the durable
/// record `sweep_manifest_orphans`'s unmarkable-manifest route depends on to
/// know a `composer.json`/`package.json` path was scaffold's on a prior run --
/// without it, `read_scaffold_manifest` (which does not exist on unfixed code)
/// cannot be called at all.
#[test]
fn scaffold_manifest_round_trips_through_write_and_read() {
    let tmp = tempfile::tempdir().expect("tempdir");
    let _cwd = crate::test_support::CwdGuard::enter(tmp.path());

    let composer = tmp.path().join("packages/php/composer.json");
    let cargo_toml = tmp.path().join("Cargo.toml");
    let write_result = write_scaffold_manifest("sample-crate", &[composer.clone(), cargo_toml.clone()]);
    let read_back = read_scaffold_manifest("sample-crate");

    write_result.expect("write scaffold manifest");
    assert_eq!(
        read_back,
        vec![cargo_toml, composer],
        "manifest must round-trip both paths in sorted order"
    );
}

/// A crate that has never had scaffold run under this mechanism (including
/// every run before it existed) must read back empty rather than erroring --
/// callers treat an empty result as "no known prior scaffold state", never as
/// proof nothing was ever scaffolded.
#[test]
fn scaffold_manifest_reads_empty_when_never_written() {
    let tmp = tempfile::tempdir().expect("tempdir");
    let _cwd = crate::test_support::CwdGuard::enter(tmp.path());

    let read_back = read_scaffold_manifest("never-scaffolded-crate");

    assert_eq!(read_back, Vec::<PathBuf>::new());
}

/// End-to-end regression for the `composer.json` orphan observed in a consumer repo: proves the
/// `write_scaffold_manifest`/`read_scaffold_manifest` wiring is what lets
/// `sweep_manifest_orphans` reclaim an unmarkable manifest a later run stops
/// emitting. Before this manifest existed, nothing ever recorded
/// `composer.json`'s path -- `write_lang_manifest` and every
/// `generate-{lang}-ownership` stage filter scaffold paths through
/// `carries_alef_marker()`, which `composer.json` never satisfies (it is
/// emitted with `generated_header: false`) -- so `sweep_manifest_orphans` was
/// always called with an empty `previous_paths` for this file and could never
/// reach it, regardless of how permissive `path_is_reclaimable` is. On unfixed
/// code, `previous_scaffold` here is empty (no prior-run record exists), so
/// `sweep_manifest_orphans` skips `composer_json` entirely and `removed` is 0,
/// failing the `assert_eq!(removed, 1, ...)` below.
#[test]
fn scaffold_manifest_wiring_lets_next_run_reclaim_dropped_manifest() {
    let tmp = tempfile::tempdir().expect("tempdir");
    let _cwd = crate::test_support::CwdGuard::enter(tmp.path());

    let package_dir = tmp.path().join("packages/php");
    std::fs::create_dir_all(&package_dir).expect("create package dir");
    let composer_json = package_dir.join("composer.json");
    std::fs::write(&composer_json, "{\n  \"name\": \"acme/demo\"\n}\n").expect("write composer.json");

    write_scaffold_manifest("sample-php", std::slice::from_ref(&composer_json)).expect("write manifest for run 1");

    let previous_scaffold = read_scaffold_manifest("sample-php");
    let keep = std::collections::HashSet::new();
    let removed =
        crate::cli::pipeline::sweep_manifest_orphans(&previous_scaffold, &keep, &[package_dir], &[]).expect("sweep");

    assert_eq!(
        removed, 1,
        "composer.json recorded by run 1's manifest must be reclaimed in run 2"
    );
    assert!(!composer_json.exists(), "orphaned composer.json must be deleted");
}

/// Regression for the `alef all` binding-orphan sweep's baseline collision: `alef all`'s
/// dedicated `all-bindings-{lang}-ownership` stage manifest (read via [`read_stage_paths`],
/// written via [`write_stage_hash`]) must be a distinct file from `<lang>.manifest`, so that
/// `write_lang_hash` -- the call `pipeline::generate` makes unconditionally for every language
/// it regenerates -- can never clobber it. Before `bin_cli/all_commands.rs` moved off
/// `read_lang_manifest`, reading `<lang>.manifest` as the "previous run" baseline after
/// `pipeline::generate` had already overwritten it with THIS run's own output meant a dropped
/// binding could never be seen as missing -- see
/// `cli::pipeline::generate::generation::lang_manifest_baseline_self_erases_before_the_orphan_sweep_ever_reads_it`
/// for the pinned reproduction of that old behaviour. This test proves the replacement baseline
/// does not share that fate.
#[test]
fn all_bindings_ownership_baseline_survives_the_lang_manifest_collision_that_used_to_erase_it() {
    let tmp = tempfile::tempdir().expect("tempdir");
    let dropped_type_file = tmp.path().join("packages/python/dropped_type.py");
    let _cwd = crate::test_support::CwdGuard::enter(tmp.path());

    let result = (|| -> anyhow::Result<(Vec<PathBuf>, Vec<PathBuf>)> {
        // Run N-1's write-back: the dedicated ownership stage records the file that still
        // existed back then.
        write_stage_hash(
            "sample",
            "all-bindings-python-ownership",
            "sources-hash-n-minus-1",
            std::slice::from_ref(&dropped_type_file),
        )?;

        // Run N: the type folded into a capsule type, so `pipeline::generate` no longer emits
        // it, and calls `write_lang_hash` (unconditionally, for every regenerated language) with
        // the smaller list -- exactly the call that used to be misread as the sweep's baseline.
        write_lang_hash("sample", "python", &test_key("lang-hash-n"), &[])?;

        let dedicated_baseline = read_stage_paths("sample", "all-bindings-python-ownership");
        let lang_manifest = read_lang_manifest("sample", "python");
        Ok((dedicated_baseline, lang_manifest))
    })();

    let (dedicated_baseline, lang_manifest) = result.expect("baseline read");
    assert_eq!(
        dedicated_baseline,
        vec![dropped_type_file],
        "the dedicated ownership stage must still report last run's file list, unaffected by \
         `write_lang_hash` overwriting the unrelated `<lang>.manifest` file"
    );
    assert!(
        lang_manifest.is_empty(),
        "`<lang>.manifest` itself is expected to have been overwritten by `write_lang_hash` -- \
         that overwrite is legitimate cache-invalidation behaviour; the fix is to stop reading \
         this file as the sweep baseline, not to change what it stores"
    );
}

/// With a correct baseline in place, a binding this run no longer emits must be swept -- the
/// behaviour that never worked while `alef all` read `<lang>.manifest` as its baseline (see
/// [`all_bindings_ownership_baseline_survives_the_lang_manifest_collision_that_used_to_erase_it`]).
#[test]
fn all_bindings_ownership_correct_baseline_sweeps_a_binding_this_run_no_longer_emits() {
    let dir = tempfile::tempdir().expect("tempdir");
    let package_dir = dir.path().join("packages/python");
    std::fs::create_dir_all(&package_dir).expect("create package dir");

    let kept_file = package_dir.join("kept_type.py");
    let dropped_file = package_dir.join("dropped_type.py");
    std::fs::write(&kept_file, "kept\n").expect("write kept file");
    let header = crate::core::hash::header(crate::core::hash::CommentStyle::Hash);
    let hashed = crate::core::hash::inject_hash_line(&header, &"0".repeat(64));
    std::fs::write(&dropped_file, &hashed).expect("write dropped file");

    let previous_paths = vec![kept_file.clone(), dropped_file.clone()];
    let mut keep = std::collections::HashSet::new();
    keep.insert(kept_file.clone());

    let removed =
        crate::cli::pipeline::sweep_manifest_orphans(&previous_paths, &keep, &[package_dir], &[]).expect("sweep");

    assert_eq!(removed, 1, "exactly the dropped binding must be swept");
    assert!(
        !dropped_file.exists(),
        "the binding this run no longer emits must be deleted"
    );
    assert!(
        kept_file.exists(),
        "a binding still in this run's keep set must survive"
    );
}

/// A missing baseline -- the state of a fresh `.alef/` cache, or of every crate on the first
/// `alef all` run after this fix ships -- must sweep nothing. Getting this backwards would
/// delete a consumer's entire generated tree on upgrade: non-negotiable.
#[test]
fn all_bindings_ownership_missing_baseline_sweeps_nothing() {
    let dir = tempfile::tempdir().expect("tempdir");
    let package_dir = dir.path().join("packages/python");
    std::fs::create_dir_all(&package_dir).expect("create package dir");

    let untouched_file = package_dir.join("untouched_type.py");
    let header = crate::core::hash::header(crate::core::hash::CommentStyle::Hash);
    let hashed = crate::core::hash::inject_hash_line(&header, &"0".repeat(64));
    std::fs::write(&untouched_file, &hashed).expect("write file");

    // No prior `write_stage_hash` call for this stage at all -- `read_stage_paths` degrades to
    // an empty `Vec`, mirroring the crate-fresh / upgrade case.
    let previous_paths = read_stage_paths(
        "crate-with-no-prior-all-bindings-ownership-record",
        "all-bindings-python-ownership",
    );
    assert!(previous_paths.is_empty(), "a never-written stage must read back empty");

    let keep = std::collections::HashSet::new();
    let removed =
        crate::cli::pipeline::sweep_manifest_orphans(&previous_paths, &keep, &[package_dir], &[]).expect("sweep");

    assert_eq!(removed, 0, "a missing baseline must sweep nothing, never everything");
    assert!(
        untouched_file.exists(),
        "a file must never be deleted on the strength of an absent baseline"
    );
}

/// A path alef never recorded owning must never be swept, even when it sits inside a directory
/// the sweep is allowed to touch and even when nothing this run keeps. Non-negotiable negative
/// control: `previous_paths` membership is the only ownership evidence `sweep_manifest_orphans`
/// accepts, and a file absent from it must be invisible to the sweep regardless of location.
#[test]
fn all_bindings_ownership_never_owned_path_is_left_untouched_even_when_present_in_sweep_root() {
    let tmp = tempfile::tempdir().expect("tempdir");
    let _cwd = crate::test_support::CwdGuard::enter(tmp.path());

    let result = (|| -> anyhow::Result<(usize, bool, bool, bool)> {
        let package_dir = tmp.path().join("packages/python");
        std::fs::create_dir_all(&package_dir)?;

        let owned_file = package_dir.join("owned_type.py");
        let header = crate::core::hash::header(crate::core::hash::CommentStyle::Hash);
        let hashed = crate::core::hash::inject_hash_line(&header, &"0".repeat(64));
        std::fs::write(&owned_file, &hashed)?;

        let foreign_file = package_dir.join("hand_written.py");
        std::fs::write(&foreign_file, "# never generated by alef\n")?;

        write_stage_hash(
            "sample",
            "all-bindings-python-ownership",
            "sources-hash",
            std::slice::from_ref(&owned_file),
        )?;
        let previous_paths = read_stage_paths("sample", "all-bindings-python-ownership");
        let leaked = previous_paths.iter().any(|path| path.ends_with("hand_written.py"));

        let keep = std::collections::HashSet::new();
        let removed = crate::cli::pipeline::sweep_manifest_orphans(&previous_paths, &keep, &[package_dir], &[])?;
        Ok((removed, owned_file.exists(), foreign_file.exists(), leaked))
    })();

    let (removed, owned_exists, foreign_exists, leaked) = result.expect("sweep");
    assert!(!leaked, "the never-owned file must not have leaked into the baseline");
    assert_eq!(removed, 1, "only the recorded, owned path may be removed");
    assert!(!owned_exists, "the recorded, no-longer-kept binding must be swept");
    assert!(
        foreign_exists,
        "a path alef never recorded owning must survive the sweep"
    );
}

/// The positive half: the snippet-coverage ledger is recognised as pure derived
/// output by the property itself, with no ownership record and no marker anywhere —
/// which is the state every consumer tree's ledger is actually in.
#[test]
fn is_alef_derived_output_recognises_the_snippet_coverage_ledger() {
    assert!(is_alef_derived_output(Path::new(
        "docs-site/src/snippets-generated/.alef-snippet-coverage.json"
    )));
    assert!(is_alef_derived_output(Path::new(
        crate::e2e::snippets::COVERAGE_MANIFEST
    )));
}

/// THE load-bearing half. A fix that simply answered `true` for every unmarkable
/// `generated_header: false` path would satisfy the ledger assertion above on its own
/// while handing alef a licence to overwrite `composer.json`, `package.json`, a zig
/// test suite and every other create-once seed — the `e2e/go/helpers_test.go`
/// incident, re-opened. Each name below is a real generated, unmarkable or
/// create-once path that a human legitimately grows past alef's placeholder, and none
/// of them may ever be classified as derived output. ~keep
#[test]
fn is_alef_derived_output_refuses_every_hand_growable_generated_path() {
    for hand_growable in [
        "packages/php/composer.json",
        "packages/node/package.json",
        "packages/java/pom.xml",
        "packages/zig/build.zig",
        "packages/zig/test/sample_core_test.zig",
        "packages/dart/test/sample_core_test.dart",
        "e2e/go/helpers_test.go",
    ] {
        assert!(
            !is_alef_derived_output(Path::new(hand_growable)),
            "{hand_growable} is content a human grows: it must never be classified as derived output"
        );
    }
}

/// The reserved-namespace conjunct is a backstop, not decoration: it is what makes a
/// mistaken future entry in `ALEF_DERIVED_OUTPUT_NAMES` inert rather than a licence to
/// clobber. Pinned by construction so the guard cannot be dropped as redundant. ~keep
#[test]
fn is_alef_derived_output_requires_the_reserved_namespace_not_only_list_membership() {
    for name in ALEF_DERIVED_OUTPUT_NAMES {
        assert!(
            name.starts_with(ALEF_RESERVED_NAME_PREFIX),
            "{name} is registered as derived output but sits outside alef's reserved namespace, \
             so the backstop silently disables it"
        );
    }
    assert!(
        !is_alef_derived_output(Path::new("docs/snippets/.alef-snippet-coverage.json.bak")),
        "a name that merely contains the ledger's name must not match"
    );
    assert!(
        !is_alef_derived_output(Path::new("docs/snippets/.alef-unregistered-state.json")),
        "the reserved prefix alone is not enough: membership in the registry is still required"
    );
}