[INFO] fetching crate qleany-rust-file-generation 2.0.1...
[INFO] testing qleany-rust-file-generation-2.0.1 against 1.100.0-beta.1+cargoflags=--release for beta-release-1.100-2
[INFO] extracting crate qleany-rust-file-generation 2.0.1 into /workspace/builds/worker-7-tc2/source
[INFO] started tweaking crates.io crate qleany-rust-file-generation 2.0.1
[INFO] finished tweaking crates.io crate qleany-rust-file-generation 2.0.1
[INFO] tweaked toml for crates.io crate qleany-rust-file-generation 2.0.1 written to /workspace/builds/worker-7-tc2/source/Cargo.toml
[INFO] validating manifest of crates.io crate qleany-rust-file-generation 2.0.1 on toolchain 1.100.0-beta.1
[INFO] running `Command { std: CARGO_HOME="/workspace/cargo-home" RUSTUP_HOME="/workspace/rustup-home" "/workspace/cargo-home/bin/cargo" "+1.100.0-beta.1" "metadata" "--manifest-path" "Cargo.toml" "--no-deps", kill_on_drop: false }`
[INFO] crate crates.io crate qleany-rust-file-generation 2.0.1 already has a lockfile, it will not be regenerated
[INFO] running `Command { std: CARGO_HOME="/workspace/cargo-home" RUSTUP_HOME="/workspace/rustup-home" "/workspace/cargo-home/bin/cargo" "+1.100.0-beta.1" "fetch" "--manifest-path" "Cargo.toml", kill_on_drop: false }`
[INFO] [stderr] warning: `package.homepage` is redundant with `package.repository`
[INFO] [stderr]   --> Cargo.toml:71:12
[INFO] [stderr]    |
[INFO] [stderr] 71 | homepage = "https://github.com/ferntech-eu/qleany"
[INFO] [stderr]    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[INFO] [stderr] ...
[INFO] [stderr] 77 | repository = "https://github.com/ferntech-eu/qleany"
[INFO] [stderr]    |              ---------------------------------------
[INFO] [stderr]    |
[INFO] [stderr]    = note: `cargo::redundant_homepage` is set to `warn` by default
[INFO] [stderr] help: consider removing `package.homepage`
[INFO] [stderr] warning: `qleany-rust-file-generation` (manifest) generated 1 warning
[INFO] running `Command { std: "docker" "create" "-v" "/var/lib/crater-agent-workspace/builds/worker-7-tc2/source:/opt/rustwide/workdir:ro,Z" "-v" "/var/lib/crater-agent-workspace/builds/worker-7-tc2/target:/opt/rustwide/target:rw,Z" "-v" "/var/lib/crater-agent-workspace/cargo-home:/opt/rustwide/cargo-home:ro,Z" "-v" "/var/lib/crater-agent-workspace/rustup-home:/opt/rustwide/rustup-home:ro,Z" "-m" "1610612736" "--network" "none" "ghcr.io/rust-lang/crates-build-env/linux@sha256:77db811e55d90add9212f6832c23229e3de5f1c1f9905e8cb07319ea15df73ac" "sleep" "infinity", kill_on_drop: false }`
[INFO] [stdout] c6042921e2f05fa53d5fc40b158d167ba21358e720edd6a71a81d341830935e0
[INFO] running `Command { std: "docker" "start" "c6042921e2f05fa53d5fc40b158d167ba21358e720edd6a71a81d341830935e0", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "inspect" "c6042921e2f05fa53d5fc40b158d167ba21358e720edd6a71a81d341830935e0", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "exec" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-w" "/opt/rustwide/workdir" "--user" "0:0" "c6042921e2f05fa53d5fc40b158d167ba21358e720edd6a71a81d341830935e0" "/opt/rustwide/cargo-home/bin/cargo" "+1.100.0-beta.1" "metadata" "--no-deps" "--format-version=1", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "inspect" "c6042921e2f05fa53d5fc40b158d167ba21358e720edd6a71a81d341830935e0", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "exec" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-e" "CARGO_INCREMENTAL=0" "-e" "RUST_BACKTRACE=full" "-e" "RUSTFLAGS=--cap-lints=warn" "-e" "RUSTDOCFLAGS=--cap-lints=warn" "-w" "/opt/rustwide/workdir" "--user" "0:0" "c6042921e2f05fa53d5fc40b158d167ba21358e720edd6a71a81d341830935e0" "/opt/rustwide/cargo-home/bin/cargo" "+1.100.0-beta.1" "build" "--frozen" "--message-format=json" "--release", kill_on_drop: false }`
[INFO] [stderr] warning: `package.homepage` is redundant with `package.repository`
[INFO] [stderr]   --> Cargo.toml:71:12
[INFO] [stderr]    |
[INFO] [stderr] 71 | homepage = "https://github.com/ferntech-eu/qleany"
[INFO] [stderr]    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[INFO] [stderr] ...
[INFO] [stderr] 77 | repository = "https://github.com/ferntech-eu/qleany"
[INFO] [stderr]    |              ---------------------------------------
[INFO] [stderr]    |
[INFO] [stderr]    = note: `cargo::redundant_homepage` is set to `warn` by default
[INFO] [stderr] help: consider removing `package.homepage`
[INFO] [stderr] warning: `qleany-rust-file-generation` (manifest) generated 1 warning
[INFO] [stderr]    Compiling libc v0.2.183
[INFO] [stderr]    Compiling regex-syntax v0.8.10
[INFO] [stderr]    Compiling zerocopy v0.8.42
[INFO] [stderr]    Compiling phf_shared v0.11.3
[INFO] [stderr]    Compiling rand v0.8.8
[INFO] [stderr]    Compiling aho-corasick v1.1.4
[INFO] [stderr]    Compiling ucd-trie v0.1.7
[INFO] [stderr]    Compiling memchr v2.8.0
[INFO] [stderr]    Compiling bstr v1.13.1
[INFO] [stderr]    Compiling serde_json v1.0.149
[INFO] [stderr]    Compiling serde v1.0.228
[INFO] [stderr]    Compiling indexmap v2.13.0
[INFO] [stderr]    Compiling im v15.1.0
[INFO] [stderr]    Compiling bitmaps v2.1.0
[INFO] [stderr]    Compiling anyhow v1.0.102
[INFO] [stderr]    Compiling deunicode v1.6.2
[INFO] [stderr]    Compiling phf v0.11.3
[INFO] [stderr]    Compiling bitflags v2.11.0
[INFO] [stderr]    Compiling futures-sink v0.3.32
[INFO] [stderr]    Compiling futures-core v0.3.32
[INFO] [stderr]    Compiling fastrand v2.3.0
[INFO] [stderr]    Compiling slug v0.1.6
[INFO] [stderr]    Compiling phf_generator v0.11.3
[INFO] [stderr]    Compiling humansize v2.1.3
[INFO] [stderr]    Compiling phf_codegen v0.11.3
[INFO] [stderr]    Compiling chrono v0.4.44
[INFO] [stderr]    Compiling pest v2.9.1
[INFO] [stderr]    Compiling include_dir_macros v0.7.4
[INFO] [stderr]    Compiling unicode-segmentation v1.13.3
[INFO] [stderr]    Compiling flume v0.12.0
[INFO] [stderr]    Compiling qleany-macros v2.0.1
[INFO] [stderr]    Compiling regex-automata v0.4.14
[INFO] [stderr]    Compiling sized-chunks v0.6.5
[INFO] [stderr]    Compiling include_dir v0.7.4
[INFO] [stderr]    Compiling qleany-naming v2.0.1
[INFO] [stderr]    Compiling getrandom v0.2.17
[INFO] [stderr]    Compiling which v8.0.6
[INFO] [stderr]    Compiling rand_core v0.6.4
[INFO] [stderr]    Compiling rand_xoshiro v0.6.0
[INFO] [stderr]    Compiling pest_meta v2.9.1
[INFO] [stderr]    Compiling pest_generator v2.9.1
[INFO] [stderr]    Compiling regex v1.12.3
[INFO] [stderr]    Compiling pest_derive v2.9.1
[INFO] [stderr]    Compiling globset v0.4.19
[INFO] [stderr]    Compiling parse-zoneinfo v0.3.1
[INFO] [stderr]    Compiling chrono-tz-build v0.3.0
[INFO] [stderr]    Compiling ignore v0.4.32
[INFO] [stderr]    Compiling ppv-lite86 v0.2.21
[INFO] [stderr]    Compiling chrono-tz v0.9.0
[INFO] [stderr]    Compiling qleany-common v2.0.1
[INFO] [stderr]    Compiling rand_chacha v0.3.1
[INFO] [stderr]    Compiling globwalk v0.9.1
[INFO] [stderr]    Compiling tera v1.20.1
[INFO] [stderr]    Compiling qleany-rust-file-generation v2.0.1 (/opt/rustwide/workdir)
[INFO] [stderr] warning: unused dependency `time`
[INFO] [stderr]   --> Cargo.toml:47:15
[INFO] [stderr]    |
[INFO] [stderr] 47 | [dependencies.time]
[INFO] [stderr]    |               ^^^^^
[INFO] [stderr]    |
[INFO] [stderr]    = note: `cargo::unused_dependencies` is set to `warn` by default
[INFO] [stderr] help: consider removing the dependency on `time`
[INFO] [stderr] warning: `qleany-rust-file-generation` (manifest) generated 1 warning
[INFO] [stderr]     Finished `release` profile [optimized] target(s) in 1m 08s
[INFO] running `Command { std: "docker" "inspect" "c6042921e2f05fa53d5fc40b158d167ba21358e720edd6a71a81d341830935e0", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "exec" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-e" "CARGO_INCREMENTAL=0" "-e" "RUST_BACKTRACE=full" "-e" "RUSTFLAGS=--cap-lints=warn" "-e" "RUSTDOCFLAGS=--cap-lints=warn" "-w" "/opt/rustwide/workdir" "--user" "0:0" "c6042921e2f05fa53d5fc40b158d167ba21358e720edd6a71a81d341830935e0" "/opt/rustwide/cargo-home/bin/cargo" "+1.100.0-beta.1" "test" "--frozen" "--no-run" "--message-format=json" "--release", kill_on_drop: false }`
[INFO] [stderr] warning: `package.homepage` is redundant with `package.repository`
[INFO] [stderr]   --> Cargo.toml:71:12
[INFO] [stderr]    |
[INFO] [stderr] 71 | homepage = "https://github.com/ferntech-eu/qleany"
[INFO] [stderr]    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[INFO] [stderr] ...
[INFO] [stderr] 77 | repository = "https://github.com/ferntech-eu/qleany"
[INFO] [stderr]    |              ---------------------------------------
[INFO] [stderr]    |
[INFO] [stderr]    = note: `cargo::redundant_homepage` is set to `warn` by default
[INFO] [stderr] help: consider removing `package.homepage`
[INFO] [stderr] warning: `qleany-rust-file-generation` (manifest) generated 1 warning
[INFO] [stderr]    Compiling qleany-rust-file-generation v2.0.1 (/opt/rustwide/workdir)
[INFO] [stderr] warning: unused dependency `time`
[INFO] [stderr]   --> Cargo.toml:47:15
[INFO] [stderr]    |
[INFO] [stderr] 47 | [dependencies.time]
[INFO] [stderr]    |               ^^^^^
[INFO] [stderr]    |
[INFO] [stderr]    = note: `cargo::unused_dependencies` is set to `warn` by default
[INFO] [stderr] help: consider removing the dependency on `time`
[INFO] [stderr] warning: `qleany-rust-file-generation` (manifest) generated 1 warning
[INFO] [stderr]     Finished `release` profile [optimized] target(s) in 8.84s
[INFO] running `Command { std: "docker" "inspect" "c6042921e2f05fa53d5fc40b158d167ba21358e720edd6a71a81d341830935e0", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "exec" "-e" "SOURCE_DIR=/opt/rustwide/workdir" "-e" "CARGO_HOME=/opt/rustwide/cargo-home" "-e" "RUSTUP_HOME=/opt/rustwide/rustup-home" "-e" "CARGO_TARGET_DIR=/opt/rustwide/target" "-e" "CARGO_INCREMENTAL=0" "-e" "RUST_BACKTRACE=full" "-e" "RUSTFLAGS=--cap-lints=warn" "-e" "RUSTDOCFLAGS=--cap-lints=warn" "-w" "/opt/rustwide/workdir" "--user" "0:0" "c6042921e2f05fa53d5fc40b158d167ba21358e720edd6a71a81d341830935e0" "/opt/rustwide/cargo-home/bin/cargo" "+1.100.0-beta.1" "test" "--frozen" "--release", kill_on_drop: false }`
[INFO] [stderr] warning: `package.homepage` is redundant with `package.repository`
[INFO] [stderr]   --> Cargo.toml:71:12
[INFO] [stderr]    |
[INFO] [stderr] 71 | homepage = "https://github.com/ferntech-eu/qleany"
[INFO] [stderr]    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[INFO] [stderr] ...
[INFO] [stderr] 77 | repository = "https://github.com/ferntech-eu/qleany"
[INFO] [stderr]    |              ---------------------------------------
[INFO] [stderr]    |
[INFO] [stderr]    = note: `cargo::redundant_homepage` is set to `warn` by default
[INFO] [stderr] help: consider removing `package.homepage`
[INFO] [stderr] warning: `qleany-rust-file-generation` (manifest) generated 1 warning
[INFO] [stderr] warning: unused dependency `time`
[INFO] [stderr]   --> Cargo.toml:47:15
[INFO] [stderr]    |
[INFO] [stderr] 47 | [dependencies.time]
[INFO] [stderr]    |               ^^^^^
[INFO] [stderr]    |
[INFO] [stderr]    = note: `cargo::unused_dependencies` is set to `warn` by default
[INFO] [stderr] help: consider removing the dependency on `time`
[INFO] [stderr] warning: `qleany-rust-file-generation` (manifest) generated 1 warning
[INFO] [stderr]     Finished `release` profile [optimized] target(s) in 0.17s
[INFO] [stderr]      Running unittests src/lib.rs (/opt/rustwide/target/release/build/qleany-rust-file-generation/d804ead8961b762a/out/rust_file_generation-d804ead8961b762a)
[INFO] [stdout] 
[INFO] [stdout] running 19 tests
[INFO] [stdout] test use_cases::common::rust_code_generator::rust_code_generator_tests::for_file_feature_without_use_cases_errors ... ok
[INFO] [stdout] test use_cases::common::rust_code_generator::rust_code_generator_tests::for_file_happy_path_feature_with_use_case_and_dtos ... ok
[INFO] [stdout] test use_cases::common::rust_code_generator::rust_code_generator_tests::for_file_returns_err_when_file_missing ... ok
[INFO] [stdout] test use_cases::common::rust_formatter::tests::test_find_rustfmt_best_effort ... ok
[INFO] [stdout] test use_cases::common::rust_code_generator::rust_code_generator_tests::for_file_various_combinations_generate_expected_items ... ok
[INFO] [stdout] test use_cases::common::rust_code_generator::rust_code_generator_tests::teksilo_list_mutations_require_the_exact_strong_owning_field ... ok
[INFO] [stdout] test use_cases::common::rust_code_generator::direct_access_lib_tests::render_direct_access_lib_lists_entities ... ok
[INFO] [stdout] test use_cases::common::rust_code_generator::tests::hashmap_store_template_pluralizes_the_entity_table_names ... ok
[INFO] [stdout] test use_cases::common::rust_code_generator::tests::test_root_cargo_tera_template ... ok
[INFO] [stdout] test use_cases::common::rust_code_generator::tests::test_entity_dtos_template_generation ... ok
[INFO] [stderr] error: 'rustfmt' is not installed for the toolchain '1.100.0-beta.1-x86_64-unknown-linux-gnu'.
[INFO] [stderr] help: run `rustup component add --toolchain 1.100.0-beta.1-x86_64-unknown-linux-gnu rustfmt` to install it
[INFO] [stdout] test use_cases::common::rust_formatter::tests::test_rustfmt_string_idempotent_when_not_found_or_on_error ... ok
[INFO] [stdout] test use_cases::common::rust_code_generator::rust_code_generator_tests::feature_use_case_uow_write_template_wires_the_guard_and_renders_valid_rust ... FAILED
[INFO] [stdout] test use_cases::common::rust_code_generator::rust_code_generator_tests::entity_table_checks_ownership_through_the_junction_index ... FAILED
[INFO] [stdout] test use_cases::common::rust_code_generator::rust_code_generator_tests::hashmap_store_indexes_exclusive_junctions_and_validates_without_walking_them ... FAILED
[INFO] [stdout] test use_cases::common::rust_code_generator::rust_code_generator_tests::entity_units_of_work_template_wires_the_guard_and_renders_valid_rust ... FAILED
[INFO] [stdout] test use_cases::common::rust_code_generator::rust_code_generator_tests::feature_use_case_uow_read_only_template_has_no_guard_and_renders_valid_rust ... FAILED
[INFO] [stdout] test use_cases::common::rust_code_generator::rust_code_generator_tests::write_guard_template_renders_valid_rust ... FAILED
[INFO] [stdout] test use_cases::common::rust_code_generator::rust_code_generator_tests::feature_use_case_uow_long_operation_write_template_wires_the_guard_and_renders_valid_rust ... FAILED
[INFO] [stdout] test use_cases::common::rust_code_generator::rust_code_generator_tests::feature_use_case_uow_long_operation_read_only_template_has_no_guard_and_renders_valid_rust ... FAILED
[INFO] [stdout] 
[INFO] [stdout] failures:
[INFO] [stdout] 
[INFO] [stdout] ---- use_cases::common::rust_code_generator::rust_code_generator_tests::feature_use_case_uow_write_template_wires_the_guard_and_renders_valid_rust stdout ----
[INFO] [stdout] 
[INFO] [stdout] thread 'use_cases::common::rust_code_generator::rust_code_generator_tests::feature_use_case_uow_write_template_wires_the_guard_and_renders_valid_rust' (1229) panicked at src/use_cases/common/rust_code_generator/rust_code_generator_tests.rs:830:5:
[INFO] [stdout] feature_use_case_uow (write, non-long-operation): rendered template output is not valid Rust:
[INFO] [stdout] error: 'rustfmt' is not installed for the toolchain '1.100.0-beta.1-x86_64-unknown-linux-gnu'.
[INFO] [stdout] help: run `rustup component add --toolchain 1.100.0-beta.1-x86_64-unknown-linux-gnu rustfmt` to install it
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] --- rendered code ---
[INFO] [stdout] // Generated by Qleany v1.0.0 from feature_use_case_uow.tera
[INFO] [stdout] 
[INFO] [stdout] use crate::use_cases::do_thing_uc::{
[INFO] [stdout]     DoThingUnitOfWorkFactoryTrait, DoThingUnitOfWorkTrait,
[INFO] [stdout] };
[INFO] [stdout] use anyhow::{Ok, Result};
[INFO] [stdout] use common::database::{db_context::DbContext, transactions::Transaction};
[INFO] [stdout] #[allow(unused_imports)]
[INFO] [stdout] use common::entities:: {
[INFO] [stdout] Thing,
[INFO] [stdout] };
[INFO] [stdout] use common::database::CommandUnitOfWork;
[INFO] [stdout] use common::database::write_guard::WriteTransactionGuard;
[INFO] [stdout] use common::event::{AllEvent, DirectAccessEntity, Event, EventBuffer, EventHub, Origin};
[INFO] [stdout] use std::cell::RefCell;
[INFO] [stdout] use std::sync::Arc;
[INFO] [stdout] use common::event::DoingEvent::DoThing;
[INFO] [stdout] #[allow(unused_imports)]
[INFO] [stdout] use common::types;
[INFO] [stdout] #[allow(unused_imports)]
[INFO] [stdout] use common::types::EntityId;
[INFO] [stdout] 
[INFO] [stdout] // Unit of work for DoThing
[INFO] [stdout] 
[INFO] [stdout] pub struct DoThingUnitOfWork {
[INFO] [stdout]     context: DbContext,
[INFO] [stdout]     transaction: Option<Transaction>,
[INFO] [stdout]     // RAII: acquired in `begin_transaction`, released via `commit`/`rollback`
[INFO] [stdout]     // clearing it — see `common::database::write_guard`'s module doc for the
[INFO] [stdout]     // single-write-transaction-per-store invariant this enforces.
[INFO] [stdout]     write_guard: Option<WriteTransactionGuard>,
[INFO] [stdout]     event_hub: Arc<EventHub>,
[INFO] [stdout]     event_buffer: RefCell<EventBuffer>,
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl DoThingUnitOfWork {
[INFO] [stdout]     pub fn new(db_context: &DbContext, event_hub: &Arc<EventHub>) -> Self {
[INFO] [stdout]         DoThingUnitOfWork {
[INFO] [stdout]             context: db_context.clone(),
[INFO] [stdout]             transaction: None,
[INFO] [stdout]             write_guard: None,
[INFO] [stdout]             event_hub: event_hub.clone(),
[INFO] [stdout]             event_buffer: RefCell::new(EventBuffer::new()),
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl CommandUnitOfWork for DoThingUnitOfWork {
[INFO] [stdout]     fn begin_transaction(&mut self) -> Result<()> {
[INFO] [stdout]         // Acquire BEFORE the write transaction itself — see
[INFO] [stdout]         // `common::database::write_guard`'s module doc.
[INFO] [stdout]         self.write_guard = Some(WriteTransactionGuard::acquire(&self.context, "do_thing")?);
[INFO] [stdout]         self.transaction = Some(Transaction::begin_write_transaction(&self.context)?);
[INFO] [stdout]         self.event_buffer.get_mut().begin_buffering();
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn commit(&mut self) -> Result<()> {
[INFO] [stdout]         self.transaction.take()
[INFO] [stdout]             .ok_or_else(|| anyhow::anyhow!("No active transaction"))?
[INFO] [stdout]             .commit()?;
[INFO] [stdout]         self.write_guard = None;
[INFO] [stdout]         for event in self.event_buffer.get_mut().flush() {
[INFO] [stdout]             self.event_hub.send_event(event);
[INFO] [stdout]         }
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn rollback(&mut self) -> Result<()> {
[INFO] [stdout]         self.transaction.take()
[INFO] [stdout]             .ok_or_else(|| anyhow::anyhow!("No active transaction"))?
[INFO] [stdout]             .rollback()?;
[INFO] [stdout]         self.write_guard = None;
[INFO] [stdout]         self.event_buffer.get_mut().discard();
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn create_savepoint(&self) -> Result<types::Savepoint> {
[INFO] [stdout]         self.transaction.as_ref()
[INFO] [stdout]             .ok_or_else(|| anyhow::anyhow!("No active transaction"))?
[INFO] [stdout]             .create_savepoint()
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn restore_to_savepoint(&mut self, savepoint: types::Savepoint) -> Result<()> {
[INFO] [stdout]         let mut transaction = self.transaction.take()
[INFO] [stdout]             .ok_or_else(|| anyhow::anyhow!("No active transaction"))?;
[INFO] [stdout]         transaction.restore_to_savepoint(savepoint)?;
[INFO] [stdout] 
[INFO] [stdout]         // Discard buffered events — savepoint restore invalidated them
[INFO] [stdout]         self.event_buffer.get_mut().discard();
[INFO] [stdout] 
[INFO] [stdout]         // Send Reset immediately (not buffered — UI must refresh now)
[INFO] [stdout]         self.event_hub.send_event(Event {
[INFO] [stdout]             origin: Origin::DirectAccess(DirectAccessEntity::All(AllEvent::Reset)),
[INFO] [stdout]             ids: vec![],
[INFO] [stdout]             data: None,
[INFO] [stdout]         });
[INFO] [stdout] 
[INFO] [stdout]         // Recreate the transaction after restoring to savepoint
[INFO] [stdout]         self.transaction = Some(transaction);
[INFO] [stdout] 
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] //TODO: adapt entities and actions to real use :
[INFO] [stdout] // Create, CreateMulti, Get, GetMulti, Update (scalar-only), UpdateMulti (scalar-only),
[INFO] [stdout] // UpdateWithRelationships, UpdateWithRelationshipsMulti,
[INFO] [stdout] // Remove, RemoveMulti, GetRelationship, GetRelationshipsFromRightIds,
[INFO] [stdout] // SetRelationship, SetRelationshipMulti, Snapshot, Restore
[INFO] [stdout] //
[INFO] [stdout] // You have here a read-write unit of work.
[INFO] [stdout] //
[INFO] [stdout] // RO means Read Only, so *RO actions should not used be here.
[INFO] [stdout] // Don't forget to set thread_safe = true for long operation's unit of work.
[INFO] [stdout] // Do not mix read-only and write actions in the same unit of work.
[INFO] [stdout] //
[INFO] [stdout] // Exactly the same macros must be set in the use case uow trait file in ../use_cases/do_thing_uc.rs
[INFO] [stdout] //
[INFO] [stdout] #[macros::uow_action(entity = "Thing", action = "Get")]
[INFO] [stdout] #[macros::uow_action(entity = "Thing", action = "GetMulti")]
[INFO] [stdout] #[macros::uow_action(entity = "Thing", action = "Snapshot")]
[INFO] [stdout] #[macros::uow_action(entity = "Thing", action = "Restore")]
[INFO] [stdout] impl DoThingUnitOfWorkTrait for DoThingUnitOfWork {
[INFO] [stdout]     fn publish_do_thing_event(&self, ids: Vec<EntityId>, data: Option<String>) {
[INFO] [stdout]         self.event_hub.send_event(Event {
[INFO] [stdout]             origin: Origin::Doing(DoThing),
[INFO] [stdout]             ids,
[INFO] [stdout]             data,
[INFO] [stdout]         });
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] pub struct DoThingUnitOfWorkFactory {
[INFO] [stdout]     context: DbContext,
[INFO] [stdout]     event_hub: Arc<EventHub>,
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl DoThingUnitOfWorkFactory {
[INFO] [stdout]     pub fn new(db_context: &DbContext, event_hub: &Arc<EventHub>) -> Self {
[INFO] [stdout]         DoThingUnitOfWorkFactory {
[INFO] [stdout]             context: db_context.clone(),
[INFO] [stdout]             event_hub: event_hub.clone(),
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl DoThingUnitOfWorkFactoryTrait for DoThingUnitOfWorkFactory {
[INFO] [stdout]     fn create(&self) -> Box<dyn DoThingUnitOfWorkTrait> {
[INFO] [stdout]         Box::new(DoThingUnitOfWork::new(&self.context, &self.event_hub))
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] stack backtrace:
[INFO] [stdout]    0:     0x5a49745cc881 - <<std[617be2ca0a6fd877]::sys::backtrace::BacktraceLock>::print::DisplayBacktrace as core[a373ad73a504971]::fmt::Display>::fmt
[INFO] [stdout]    1:     0x5a49745e469a - core[a373ad73a504971]::fmt::write
[INFO] [stdout]    2:     0x5a49745d10dc - <alloc[2e1b2a31b91974c5]::vec::Vec<u8> as core[a373ad73a504971]::io::write::Write>::write_fmt
[INFO] [stdout]    3:     0x5a49745a75d6 - std[617be2ca0a6fd877]::panicking::default_hook::{closure#0}
[INFO] [stdout]    4:     0x5a49745c4669 - std[617be2ca0a6fd877]::panicking::default_hook
[INFO] [stdout]    5:     0x5a497438fc20 - test[aada8e7e76ba119b]::test_main_inner::{closure#0}
[INFO] [stdout]    6:     0x5a49745c4822 - std[617be2ca0a6fd877]::panicking::panic_with_hook
[INFO] [stdout]    7:     0x5a49745a7682 - std[617be2ca0a6fd877]::panicking::panic_handler::{closure#0}
[INFO] [stdout]    8:     0x5a497459fc79 - std[617be2ca0a6fd877]::sys::backtrace::__rust_end_short_backtrace::<std[617be2ca0a6fd877]::panicking::panic_handler::{closure#0}, !>
[INFO] [stdout]    9:     0x5a49745a839d - __rustc[ea4f61d10b84890b]::rust_begin_unwind
[INFO] [stdout]   10:     0x5a49745e4ebc - core[a373ad73a504971]::panicking::panic_fmt
[INFO] [stdout]   11:     0x5a497430eb66 - rust_file_generation[bc330dc3ad16d42f]::use_cases::common::rust_code_generator::rust_code_generator_tests::assert_is_valid_rust
[INFO] [stdout]   12:     0x5a4974320dd8 - <rust_file_generation[bc330dc3ad16d42f]::use_cases::common::rust_code_generator::rust_code_generator_tests::feature_use_case_uow_write_template_wires_the_guard_and_renders_valid_rust::{closure#0} as core[a373ad73a504971]::ops::function::FnOnce<()>>::call_once
[INFO] [stdout]   13:     0x5a497438306b - test[aada8e7e76ba119b]::__rust_begin_short_backtrace::<core[a373ad73a504971]::result::Result<(), alloc[2e1b2a31b91974c5]::string::String>, fn() -> core[a373ad73a504971]::result::Result<(), alloc[2e1b2a31b91974c5]::string::String>>
[INFO] [stdout]   14:     0x5a4974390245 - test[aada8e7e76ba119b]::run_test::{closure#0}
[INFO] [stdout]   15:     0x5a4974389b34 - std[617be2ca0a6fd877]::sys::backtrace::__rust_begin_short_backtrace::<test[aada8e7e76ba119b]::run_test::{closure#1}, ()>
[INFO] [stdout]   16:     0x5a49743932d2 - <std[617be2ca0a6fd877]::thread::lifecycle::spawn_unchecked<test[aada8e7e76ba119b]::run_test::{closure#1}, ()>::{closure#1} as core[a373ad73a504971]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0}
[INFO] [stdout]   17:     0x5a49745cba6f - <std[617be2ca0a6fd877]::sys::thread::unix::Thread>::new::thread_start
[INFO] [stdout]   18:     0x78bb6f0cbdfa - <unknown>
[INFO] [stdout]   19:     0x78bb6f15f3d4 - clone
[INFO] [stdout]   20:                0x0 - <unknown>
[INFO] [stdout] 
[INFO] [stdout] ---- use_cases::common::rust_code_generator::rust_code_generator_tests::entity_table_checks_ownership_through_the_junction_index stdout ----
[INFO] [stdout] 
[INFO] [stdout] thread 'use_cases::common::rust_code_generator::rust_code_generator_tests::entity_table_checks_ownership_through_the_junction_index' (1224) panicked at src/use_cases/common/rust_code_generator/rust_code_generator_tests.rs:830:5:
[INFO] [stdout] common_entity_table: rendered template output is not valid Rust:
[INFO] [stdout] error: 'rustfmt' is not installed for the toolchain '1.100.0-beta.1-x86_64-unknown-linux-gnu'.
[INFO] [stdout] help: run `rustup component add --toolchain 1.100.0-beta.1-x86_64-unknown-linux-gnu rustfmt` to install it
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] --- rendered code ---
[INFO] [stdout] // Generated by Qleany v1.0.0 from common_entity_table.tera
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] // ═══════════════════════════════════════════════════════════════════════
[INFO] [stdout] // Entity WITH forward relationships — explicit struct implementation
[INFO] [stdout] // ═══════════════════════════════════════════════════════════════════════
[INFO] [stdout] use crate::database::hashmap_store::{
[INFO] [stdout]     HashMapJunction, HashMapStore, delete_from_backward_junction, junction_get, junction_remove,
[INFO] [stdout]     junction_set, read_or_recover, write_or_recover,
[INFO] [stdout] };
[INFO] [stdout] use crate::entities::Folder;
[INFO] [stdout] use crate::error::RepositoryError;
[INFO] [stdout] use crate::types::EntityId;
[INFO] [stdout] use crate::{impl_relationship_methods, impl_write_relationship_methods};
[INFO] [stdout] use std::sync::RwLock;
[INFO] [stdout] 
[INFO] [stdout] use super::folder_repository::FolderRelationshipField;
[INFO] [stdout] use super::folder_repository::FolderTable;
[INFO] [stdout] use super::folder_repository::FolderTableRO;
[INFO] [stdout] 
[INFO] [stdout] pub struct FolderHashMapTable<'a> {
[INFO] [stdout]     store: &'a HashMapStore,
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl<'a> FolderHashMapTable<'a> {
[INFO] [stdout]     pub fn new(store: &'a HashMapStore) -> Self {
[INFO] [stdout]         Self { store }
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn resolve_junction(
[INFO] [stdout]         &self,
[INFO] [stdout]         field: &FolderRelationshipField,
[INFO] [stdout]     ) -> &RwLock<HashMapJunction> {
[INFO] [stdout]         match field {
[INFO] [stdout]             
[INFO] [stdout]             FolderRelationshipField::Children => &self.store.jn_folder_from_folder_children,
[INFO] [stdout]             FolderRelationshipField::Lead => &self.store.jn_tag_from_folder_lead,
[INFO] [stdout]             FolderRelationshipField::Things => &self.store.jn_thing_from_folder_things,
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn relationship_source_exists(&self, id: &EntityId) -> bool {
[INFO] [stdout]         read_or_recover(&self.store.folders).contains_key(id)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn relationship_is_ordered(&self, field: &FolderRelationshipField) -> bool {
[INFO] [stdout]         match field {
[INFO] [stdout]             
[INFO] [stdout]             FolderRelationshipField::Children => false,
[INFO] [stdout]             FolderRelationshipField::Lead => false,
[INFO] [stdout]             FolderRelationshipField::Things => false,
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Validate a batch of row replacements for one relationship before any is
[INFO] [stdout]     /// written. Costs O(targets in `rows`): exclusivity is checked against the
[INFO] [stdout]     /// junction's reverse index, never by walking the relationship's other rows.
[INFO] [stdout]     fn validate_relationship_updates(
[INFO] [stdout]         &self,
[INFO] [stdout]         field: &FolderRelationshipField,
[INFO] [stdout]         rows: &[(EntityId, Vec<EntityId>)],
[INFO] [stdout]         _creating_ids: &[EntityId],
[INFO] [stdout]     ) -> Result<(), RepositoryError> {
[INFO] [stdout]         let scalar = match field {
[INFO] [stdout]             
[INFO] [stdout]             FolderRelationshipField::Children => {
[INFO] [stdout]                 let targets = read_or_recover(&self.store.folders);
[INFO] [stdout]                 let mut missing: Vec<_> = rows.iter().flat_map(|(_, ids)| ids).copied()
[INFO] [stdout]                     .filter(|id| !targets.contains_key(id)).collect();
[INFO] [stdout]                 if !missing.is_empty() {
[INFO] [stdout]                     // Self-referential: a row of this very batch may point at
[INFO] [stdout]                     // another row being created alongside it.
[INFO] [stdout]                     let creating: std::collections::HashSet<EntityId> = _creating_ids.iter().copied().collect();
[INFO] [stdout]                     missing.retain(|id| !creating.contains(id));
[INFO] [stdout]                 }
[INFO] [stdout]                 if !missing.is_empty() {
[INFO] [stdout]                     return Err(RepositoryError::MissingRelationshipTarget { operation: "relationship update", ids: missing });
[INFO] [stdout]                 }
[INFO] [stdout]                 false
[INFO] [stdout]             }
[INFO] [stdout]             FolderRelationshipField::Lead => {
[INFO] [stdout]                 let targets = read_or_recover(&self.store.tags);
[INFO] [stdout]                 let missing: Vec<_> = rows.iter().flat_map(|(_, ids)| ids).copied()
[INFO] [stdout]                     .filter(|id| !targets.contains_key(id)).collect();
[INFO] [stdout]                 if !missing.is_empty() {
[INFO] [stdout]                     return Err(RepositoryError::MissingRelationshipTarget { operation: "relationship update", ids: missing });
[INFO] [stdout]                 }
[INFO] [stdout]                 true
[INFO] [stdout]             }
[INFO] [stdout]             FolderRelationshipField::Things => {
[INFO] [stdout]                 let targets = read_or_recover(&self.store.things);
[INFO] [stdout]                 let missing: Vec<_> = rows.iter().flat_map(|(_, ids)| ids).copied()
[INFO] [stdout]                     .filter(|id| !targets.contains_key(id)).collect();
[INFO] [stdout]                 if !missing.is_empty() {
[INFO] [stdout]                     return Err(RepositoryError::MissingRelationshipTarget { operation: "relationship update", ids: missing });
[INFO] [stdout]                 }
[INFO] [stdout]                 false
[INFO] [stdout]             }
[INFO] [stdout]         };
[INFO] [stdout]         crate::database::hashmap_store::validate_junction_updates(self.resolve_junction(field), rows, scalar)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn hydrate(&self, entity: &mut Folder) {
[INFO] [stdout]         
[INFO] [stdout]         entity.things = junction_get(
[INFO] [stdout]             &self.store.jn_thing_from_folder_things,
[INFO] [stdout]             &entity.id,
[INFO] [stdout]         );
[INFO] [stdout]         entity.lead = junction_get(
[INFO] [stdout]             &self.store.jn_tag_from_folder_lead,
[INFO] [stdout]             &entity.id,
[INFO] [stdout]         ).into_iter().next();
[INFO] [stdout]         entity.children = junction_get(
[INFO] [stdout]             &self.store.jn_folder_from_folder_children,
[INFO] [stdout]             &entity.id,
[INFO] [stdout]         );
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl<'a> FolderTable for FolderHashMapTable<'a> {
[INFO] [stdout]     fn create(&mut self, entity: &Folder) -> Result<Folder, RepositoryError> {
[INFO] [stdout]         self.create_multi(std::slice::from_ref(entity))
[INFO] [stdout]             .and_then(|v| {
[INFO] [stdout]                 // `create_multi` is contracted to return one row per input, so this
[INFO] [stdout]                 // is unreachable — but it used to be `.unwrap()`, and a store that
[INFO] [stdout]                 // ever broke that contract would take the whole process down rather
[INFO] [stdout]                 // than fail the one call that noticed.
[INFO] [stdout]                 v.into_iter().next().ok_or_else(|| {
[INFO] [stdout]                     RepositoryError::Other(anyhow::anyhow!(
[INFO] [stdout]                         "create: the store returned no row for the entity it was given"
[INFO] [stdout]                     ))
[INFO] [stdout]                 })
[INFO] [stdout]             })
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn create_multi(&mut self, entities: &[Folder]) -> Result<Vec<Folder>, RepositoryError> {
[INFO] [stdout]         // Assign identities first, then validate the whole batch without holding
[INFO] [stdout]         // the source table lock (self-referential relationships read that table).
[INFO] [stdout]         let entities: Vec<_> = entities.iter().map(|entity| {
[INFO] [stdout]             let mut entity = entity.clone();
[INFO] [stdout]             if entity.id == EntityId::default() {
[INFO] [stdout]                 entity.id = self.store.next_id("folder");
[INFO] [stdout]             }
[INFO] [stdout]             entity
[INFO] [stdout]         }).collect();
[INFO] [stdout]         
[INFO] [stdout]         self.validate_relationship_updates(
[INFO] [stdout]             &FolderRelationshipField::Things,
[INFO] [stdout]             &entities.iter().map(|e| (e.id, e.things.clone())).collect::<Vec<_>>(),
[INFO] [stdout]             &entities.iter().map(|e| e.id).collect::<Vec<_>>(),
[INFO] [stdout]         )?;
[INFO] [stdout]         self.validate_relationship_updates(
[INFO] [stdout]             &FolderRelationshipField::Children,
[INFO] [stdout]             &entities.iter().map(|e| (e.id, e.children.clone())).collect::<Vec<_>>(),
[INFO] [stdout]             &entities.iter().map(|e| e.id).collect::<Vec<_>>(),
[INFO] [stdout]         )?;
[INFO] [stdout]         let mut created = Vec::with_capacity(entities.len());
[INFO] [stdout]         let mut folder_map = write_or_recover(&self.store.folders);
[INFO] [stdout] 
[INFO] [stdout]         for entity in &entities {
[INFO] [stdout]             let new_entity = if entity.id == EntityId::default() {
[INFO] [stdout]                 let id = self.store.next_id("folder");
[INFO] [stdout]                 Folder { id, ..entity.clone() }
[INFO] [stdout]             } else {
[INFO] [stdout]                 if folder_map.contains_key(&entity.id) {
[INFO] [stdout]                     return Err(RepositoryError::DuplicateId { entity: "Folder", id: entity.id });
[INFO] [stdout]                 }
[INFO] [stdout]                 entity.clone()
[INFO] [stdout]             };
[INFO] [stdout] 
[INFO] [stdout]             
[INFO] [stdout]             // one-to-one constraint check: ensure lead is not already referenced by another folder
[INFO] [stdout]             // (the junction's reverse index answers in one lookup, never by walking its rows)
[INFO] [stdout]             {
[INFO] [stdout]                 let jn = read_or_recover(&self.store.jn_tag_from_folder_lead);
[INFO] [stdout]                 // A let-chain rather than nested `if`s: edition 2024 has them, and
[INFO] [stdout]                 // clippy::collapsible_if flags the nested form.
[INFO] [stdout]                 if let Some(ref right_id) = new_entity.lead
[INFO] [stdout]                     && let Some(existing_id) = jn.sources_of(right_id).into_iter().find(|source| *source != new_entity.id)
[INFO] [stdout]                 {
[INFO] [stdout]                     return Err(RepositoryError::ConstraintViolation(format!("One-to-one constraint violation: Tag {} is already referenced by Folder {}", right_id, existing_id)));
[INFO] [stdout]                 }
[INFO] [stdout]             }
[INFO] [stdout] 
[INFO] [stdout]             folder_map.insert(new_entity.id, new_entity.clone());
[INFO] [stdout] 
[INFO] [stdout]             
[INFO] [stdout]             junction_set(
[INFO] [stdout]                 &self.store.jn_folder_from_folder_children,
[INFO] [stdout]                 new_entity.id,
[INFO] [stdout]                 new_entity.children.clone(),
[INFO] [stdout]             );
[INFO] [stdout]             junction_set(
[INFO] [stdout]                 &self.store.jn_tag_from_folder_lead,
[INFO] [stdout]                 new_entity.id,
[INFO] [stdout]                 new_entity.lead.into_iter().collect::<Vec<EntityId>>(),
[INFO] [stdout]             );
[INFO] [stdout]             junction_set(
[INFO] [stdout]                 &self.store.jn_thing_from_folder_things,
[INFO] [stdout]                 new_entity.id,
[INFO] [stdout]                 new_entity.things.clone(),
[INFO] [stdout]             );
[INFO] [stdout] 
[INFO] [stdout]             created.push(new_entity);
[INFO] [stdout]         }
[INFO] [stdout]         Ok(created)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn get(&self, id: &EntityId) -> Result<Option<Folder>, RepositoryError> {
[INFO] [stdout]         let folder_map = read_or_recover(&self.store.folders);
[INFO] [stdout]         match folder_map.get(id) {
[INFO] [stdout]             Some(entity) => {
[INFO] [stdout]                 let mut e = entity.clone();
[INFO] [stdout]                 drop(folder_map);
[INFO] [stdout]                 self.hydrate(&mut e);
[INFO] [stdout]                 Ok(Some(e))
[INFO] [stdout]             }
[INFO] [stdout]             None => Ok(None),
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn get_multi(&self, ids: &[EntityId]) -> Result<Vec<Option<Folder>>, RepositoryError> {
[INFO] [stdout]         let mut result = Vec::with_capacity(ids.len());
[INFO] [stdout]         for id in ids {
[INFO] [stdout]             result.push(self.get(id)?);
[INFO] [stdout]         }
[INFO] [stdout]         Ok(result)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn get_all(&self) -> Result<Vec<Folder>, RepositoryError> {
[INFO] [stdout]         let folder_map = read_or_recover(&self.store.folders);
[INFO] [stdout]         let entries: Vec<Folder> = folder_map.values().cloned().collect();
[INFO] [stdout]         drop(folder_map);
[INFO] [stdout]         let mut result = Vec::with_capacity(entries.len());
[INFO] [stdout]         for mut entity in entries {
[INFO] [stdout]             self.hydrate(&mut entity);
[INFO] [stdout]             result.push(entity);
[INFO] [stdout]         }
[INFO] [stdout]         Ok(result)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn update(&mut self, entity: &Folder) -> Result<Folder, RepositoryError> {
[INFO] [stdout]         self.update_multi(std::slice::from_ref(entity))
[INFO] [stdout]             .and_then(|v| {
[INFO] [stdout]                 // `update_multi` is contracted to return one row per input, so this
[INFO] [stdout]                 // is unreachable — but it used to be `.unwrap()`, and a store that
[INFO] [stdout]                 // ever broke that contract would take the whole process down rather
[INFO] [stdout]                 // than fail the one call that noticed.
[INFO] [stdout]                 v.into_iter().next().ok_or_else(|| {
[INFO] [stdout]                     RepositoryError::Other(anyhow::anyhow!(
[INFO] [stdout]                         "update: the store returned no row for the entity it was given"
[INFO] [stdout]                     ))
[INFO] [stdout]                 })
[INFO] [stdout]             })
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     // Scalar-only update: writes entity data but does NOT touch junction tables.
[INFO] [stdout]     fn update_multi(&mut self, entities: &[Folder]) -> Result<Vec<Folder>, RepositoryError> {
[INFO] [stdout]         let mut folder_map = write_or_recover(&self.store.folders);
[INFO] [stdout]         for entity in entities {
[INFO] [stdout]             folder_map.insert(entity.id, entity.clone());
[INFO] [stdout]         }
[INFO] [stdout]         drop(folder_map);
[INFO] [stdout]         let ids: Vec<EntityId> = entities.iter().map(|e| e.id).collect();
[INFO] [stdout]         let result = self.get_multi(&ids)?;
[INFO] [stdout]         Ok(result.into_iter().flatten().collect())
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn update_with_relationships(&mut self, entity: &Folder) -> Result<Folder, RepositoryError> {
[INFO] [stdout]         self.update_with_relationships_multi(std::slice::from_ref(entity))
[INFO] [stdout]             .and_then(|v| {
[INFO] [stdout]                 // `update_with_relationships_multi` is contracted to return one row per input, so this
[INFO] [stdout]                 // is unreachable — but it used to be `.unwrap()`, and a store that
[INFO] [stdout]                 // ever broke that contract would take the whole process down rather
[INFO] [stdout]                 // than fail the one call that noticed.
[INFO] [stdout]                 v.into_iter().next().ok_or_else(|| {
[INFO] [stdout]                     RepositoryError::Other(anyhow::anyhow!(
[INFO] [stdout]                         "update_with_relationships: the store returned no row for the entity it was given"
[INFO] [stdout]                     ))
[INFO] [stdout]                 })
[INFO] [stdout]             })
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn update_with_relationships_multi(&mut self, entities: &[Folder]) -> Result<Vec<Folder>, RepositoryError> {
[INFO] [stdout]         
[INFO] [stdout]         self.validate_relationship_updates(
[INFO] [stdout]             &FolderRelationshipField::Things,
[INFO] [stdout]             &entities.iter().map(|e| (e.id, e.things.clone())).collect::<Vec<_>>(),
[INFO] [stdout]             &[],
[INFO] [stdout]         )?;
[INFO] [stdout]         self.validate_relationship_updates(
[INFO] [stdout]             &FolderRelationshipField::Children,
[INFO] [stdout]             &entities.iter().map(|e| (e.id, e.children.clone())).collect::<Vec<_>>(),
[INFO] [stdout]             &[],
[INFO] [stdout]         )?;
[INFO] [stdout]         let mut folder_map = write_or_recover(&self.store.folders);
[INFO] [stdout]         for entity in entities {
[INFO] [stdout]             
[INFO] [stdout]             // one-to-one constraint check: ensure lead is not already referenced by another folder
[INFO] [stdout]             // (the junction's reverse index answers in one lookup, never by walking its rows)
[INFO] [stdout]             {
[INFO] [stdout]                 let jn = read_or_recover(&self.store.jn_tag_from_folder_lead);
[INFO] [stdout]                 // A let-chain rather than nested `if`s: edition 2024 has them, and
[INFO] [stdout]                 // clippy::collapsible_if flags the nested form.
[INFO] [stdout]                 if let Some(ref right_id) = entity.lead
[INFO] [stdout]                     && let Some(existing_id) = jn.sources_of(right_id).into_iter().find(|source| *source != entity.id)
[INFO] [stdout]                 {
[INFO] [stdout]                     return Err(RepositoryError::ConstraintViolation(format!("One-to-one constraint violation: Tag {} is already referenced by Folder {}", right_id, existing_id)));
[INFO] [stdout]                 }
[INFO] [stdout]             }
[INFO] [stdout]             folder_map.insert(entity.id, entity.clone());
[INFO] [stdout]             
[INFO] [stdout]             junction_set(
[INFO] [stdout]                 &self.store.jn_thing_from_folder_things,
[INFO] [stdout]                 entity.id,
[INFO] [stdout]                 entity.things.clone(),
[INFO] [stdout]             );
[INFO] [stdout]             junction_set(
[INFO] [stdout]                 &self.store.jn_tag_from_folder_lead,
[INFO] [stdout]                 entity.id,
[INFO] [stdout]                 entity.lead.into_iter().collect::<Vec<EntityId>>(),
[INFO] [stdout]             );
[INFO] [stdout]             junction_set(
[INFO] [stdout]                 &self.store.jn_folder_from_folder_children,
[INFO] [stdout]                 entity.id,
[INFO] [stdout]                 entity.children.clone(),
[INFO] [stdout]             );
[INFO] [stdout]         }
[INFO] [stdout]         drop(folder_map);
[INFO] [stdout]         let ids: Vec<EntityId> = entities.iter().map(|e| e.id).collect();
[INFO] [stdout]         let result = self.get_multi(&ids)?;
[INFO] [stdout]         Ok(result.into_iter().flatten().collect())
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn remove(&mut self, id: &EntityId) -> Result<(), RepositoryError> {
[INFO] [stdout]         self.remove_multi(std::slice::from_ref(id))
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn remove_multi(&mut self, ids: &[EntityId]) -> Result<(), RepositoryError> {
[INFO] [stdout]         let mut folder_map = write_or_recover(&self.store.folders);
[INFO] [stdout]         for id in ids {
[INFO] [stdout]             folder_map.remove(id);
[INFO] [stdout] 
[INFO] [stdout]             // Remove forward junction entries
[INFO] [stdout]             
[INFO] [stdout]             junction_remove(&self.store.jn_thing_from_folder_things, id);
[INFO] [stdout]             junction_remove(&self.store.jn_tag_from_folder_lead, id);
[INFO] [stdout]             junction_remove(&self.store.jn_folder_from_folder_children, id);
[INFO] [stdout] 
[INFO] [stdout]             // Clean up backward references (uses the owning entity's forward junction)
[INFO] [stdout]             
[INFO] [stdout]             // self-referential backward: same forward junction
[INFO] [stdout]             delete_from_backward_junction(&self.store.jn_folder_from_folder_children, id);
[INFO] [stdout]         }
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     impl_write_relationship_methods!(FolderHashMapTable<'a>, FolderRelationshipField);
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] pub struct FolderHashMapTableRO<'a> {
[INFO] [stdout]     store: &'a HashMapStore,
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl<'a> FolderHashMapTableRO<'a> {
[INFO] [stdout]     pub fn new(store: &'a HashMapStore) -> Self {
[INFO] [stdout]         Self { store }
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn resolve_junction(
[INFO] [stdout]         &self,
[INFO] [stdout]         field: &FolderRelationshipField,
[INFO] [stdout]     ) -> &RwLock<HashMapJunction> {
[INFO] [stdout]         match field {
[INFO] [stdout]             
[INFO] [stdout]             FolderRelationshipField::Children => &self.store.jn_folder_from_folder_children,
[INFO] [stdout]             FolderRelationshipField::Lead => &self.store.jn_tag_from_folder_lead,
[INFO] [stdout]             FolderRelationshipField::Things => &self.store.jn_thing_from_folder_things,
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn hydrate(&self, entity: &mut Folder) {
[INFO] [stdout]         
[INFO] [stdout]         entity.things = junction_get(
[INFO] [stdout]             &self.store.jn_thing_from_folder_things,
[INFO] [stdout]             &entity.id,
[INFO] [stdout]         );
[INFO] [stdout]         entity.lead = junction_get(
[INFO] [stdout]             &self.store.jn_tag_from_folder_lead,
[INFO] [stdout]             &entity.id,
[INFO] [stdout]         ).into_iter().next();
[INFO] [stdout]         entity.children = junction_get(
[INFO] [stdout]             &self.store.jn_folder_from_folder_children,
[INFO] [stdout]             &entity.id,
[INFO] [stdout]         );
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl<'a> FolderTableRO for FolderHashMapTableRO<'a> {
[INFO] [stdout]     fn get(&self, id: &EntityId) -> Result<Option<Folder>, RepositoryError> {
[INFO] [stdout]         let folder_map = read_or_recover(&self.store.folders);
[INFO] [stdout]         match folder_map.get(id) {
[INFO] [stdout]             Some(entity) => {
[INFO] [stdout]                 let mut e = entity.clone();
[INFO] [stdout]                 drop(folder_map);
[INFO] [stdout]                 self.hydrate(&mut e);
[INFO] [stdout]                 Ok(Some(e))
[INFO] [stdout]             }
[INFO] [stdout]             None => Ok(None),
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn get_multi(&self, ids: &[EntityId]) -> Result<Vec<Option<Folder>>, RepositoryError> {
[INFO] [stdout]         let mut result = Vec::with_capacity(ids.len());
[INFO] [stdout]         for id in ids {
[INFO] [stdout]             result.push(self.get(id)?);
[INFO] [stdout]         }
[INFO] [stdout]         Ok(result)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn get_all(&self) -> Result<Vec<Folder>, RepositoryError> {
[INFO] [stdout]         let folder_map = read_or_recover(&self.store.folders);
[INFO] [stdout]         let entries: Vec<Folder> = folder_map.values().cloned().collect();
[INFO] [stdout]         drop(folder_map);
[INFO] [stdout]         let mut result = Vec::with_capacity(entries.len());
[INFO] [stdout]         for mut entity in entries {
[INFO] [stdout]             self.hydrate(&mut entity);
[INFO] [stdout]             result.push(entity);
[INFO] [stdout]         }
[INFO] [stdout]         Ok(result)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     impl_relationship_methods!(FolderHashMapTableRO<'a>, FolderRelationshipField);
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] stack backtrace:
[INFO] [stdout]    0:     0x5a49745cc881 - <<std[617be2ca0a6fd877]::sys::backtrace::BacktraceLock>::print::DisplayBacktrace as core[a373ad73a504971]::fmt::Display>::fmt
[INFO] [stdout]    1:     0x5a49745e469a - core[a373ad73a504971]::fmt::write
[INFO] [stdout]    2:     0x5a49745d10dc - <alloc[2e1b2a31b91974c5]::vec::Vec<u8> as core[a373ad73a504971]::io::write::Write>::write_fmt
[INFO] [stdout]    3:     0x5a49745a75d6 - std[617be2ca0a6fd877]::panicking::default_hook::{closure#0}
[INFO] [stdout]    4:     0x5a49745c4669 - std[617be2ca0a6fd877]::panicking::default_hook
[INFO] [stdout]    5:     0x5a497438fc20 - test[aada8e7e76ba119b]::test_main_inner::{closure#0}
[INFO] [stdout]    6:     0x5a49745c4822 - std[617be2ca0a6fd877]::panicking::panic_with_hook
[INFO] [stdout]    7:     0x5a49745a7682 - std[617be2ca0a6fd877]::panicking::panic_handler::{closure#0}
[INFO] [stdout]    8:     0x5a497459fc79 - std[617be2ca0a6fd877]::sys::backtrace::__rust_end_short_backtrace::<std[617be2ca0a6fd877]::panicking::panic_handler::{closure#0}, !>
[INFO] [stdout]    9:     0x5a49745a839d - __rustc[ea4f61d10b84890b]::rust_begin_unwind
[INFO] [stdout]   10:     0x5a49745e4ebc - core[a373ad73a504971]::panicking::panic_fmt
[INFO] [stdout]   11:     0x5a497430eb66 - rust_file_generation[bc330dc3ad16d42f]::use_cases::common::rust_code_generator::rust_code_generator_tests::assert_is_valid_rust
[INFO] [stdout]   12:     0x5a497431f3a1 - <rust_file_generation[bc330dc3ad16d42f]::use_cases::common::rust_code_generator::rust_code_generator_tests::entity_table_checks_ownership_through_the_junction_index::{closure#0} as core[a373ad73a504971]::ops::function::FnOnce<()>>::call_once
[INFO] [stdout]   13:     0x5a497438306b - test[aada8e7e76ba119b]::__rust_begin_short_backtrace::<core[a373ad73a504971]::result::Result<(), alloc[2e1b2a31b91974c5]::string::String>, fn() -> core[a373ad73a504971]::result::Result<(), alloc[2e1b2a31b91974c5]::string::String>>
[INFO] [stdout]   14:     0x5a4974390245 - test[aada8e7e76ba119b]::run_test::{closure#0}
[INFO] [stdout]   15:     0x5a4974389b34 - std[617be2ca0a6fd877]::sys::backtrace::__rust_begin_short_backtrace::<test[aada8e7e76ba119b]::run_test::{closure#1}, ()>
[INFO] [stdout]   16:     0x5a49743932d2 - <std[617be2ca0a6fd877]::thread::lifecycle::spawn_unchecked<test[aada8e7e76ba119b]::run_test::{closure#1}, ()>::{closure#1} as core[a373ad73a504971]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0}
[INFO] [stdout]   17:     0x5a49745cba6f - <std[617be2ca0a6fd877]::sys::thread::unix::Thread>::new::thread_start
[INFO] [stdout]   18:     0x78bb6f0cbdfa - <unknown>
[INFO] [stdout]   19:     0x78bb6f15f3d4 - clone
[INFO] [stdout]   20:                0x0 - <unknown>
[INFO] [stdout] 
[INFO] [stdout] ---- use_cases::common::rust_code_generator::rust_code_generator_tests::hashmap_store_indexes_exclusive_junctions_and_validates_without_walking_them stdout ----
[INFO] [stdout] 
[INFO] [stdout] thread 'use_cases::common::rust_code_generator::rust_code_generator_tests::hashmap_store_indexes_exclusive_junctions_and_validates_without_walking_them' (1234) panicked at src/use_cases/common/rust_code_generator/rust_code_generator_tests.rs:830:5:
[INFO] [stdout] hashmap_store: rendered template output is not valid Rust:
[INFO] [stdout] error: 'rustfmt' is not installed for the toolchain '1.100.0-beta.1-x86_64-unknown-linux-gnu'.
[INFO] [stdout] help: run `rustup component add --toolchain 1.100.0-beta.1-x86_64-unknown-linux-gnu rustfmt` to install it
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] --- rendered code ---
[INFO] [stdout] // Generated by Qleany v1.0.0 from hashmap_store.tera
[INFO] [stdout] 
[INFO] [stdout] //! HashMap-based storage backend with O(1) snapshots.
[INFO] [stdout] //!
[INFO] [stdout] //! Uses `im::HashMap` (persistent/immutable data structure with structural sharing)
[INFO] [stdout] //! so that cloning the store for savepoints and undo is O(1).
[INFO] [stdout] //! The store uses `RwLock` for interior mutability and thread safety,
[INFO] [stdout] //! allowing `&HashMapStore` to be shared across threads via `Arc`.
[INFO] [stdout] 
[INFO] [stdout] use crate::entities::*;
[INFO] [stdout] use crate::snapshot::{StoreSnapshot, StoreSnapshotTrait};
[INFO] [stdout] use crate::types::EntityId;
[INFO] [stdout] use im::HashMap;
[INFO] [stdout] use std::sync::RwLock;
[INFO] [stdout] 
[INFO] [stdout] // ─────────────────────────────────────────────────────────────────────────────
[INFO] [stdout] // The Store
[INFO] [stdout] // ─────────────────────────────────────────────────────────────────────────────
[INFO] [stdout] 
[INFO] [stdout] #[derive(Debug)]
[INFO] [stdout] pub struct HashMapStore {
[INFO] [stdout]     // ── Entity tables ──────────────────────────────────────────────────
[INFO] [stdout]     pub folders: RwLock<HashMap<EntityId, Folder>>,
[INFO] [stdout]     pub things: RwLock<HashMap<EntityId, Thing>>,
[INFO] [stdout]     pub tags: RwLock<HashMap<EntityId, Tag>>,
[INFO] [stdout] 
[INFO] [stdout]     // ── Junction tables (one per forward relationship, shared for backward cleanup) ─
[INFO] [stdout]     pub jn_folder_from_folder_children: RwLock<HashMapJunction>,
[INFO] [stdout]     pub jn_tag_from_folder_lead: RwLock<HashMapJunction>,
[INFO] [stdout]     pub jn_thing_from_folder_things: RwLock<HashMapJunction>,
[INFO] [stdout]     pub jn_tag_from_thing_tags: RwLock<HashMapJunction>,
[INFO] [stdout] 
[INFO] [stdout]     // ── ID counters (one per entity type) ──────────────────────────
[INFO] [stdout]     pub counters: RwLock<std::collections::HashMap<String, EntityId>>,
[INFO] [stdout] 
[INFO] [stdout]     // ── Savepoints ──────────────────────────────────────────────────
[INFO] [stdout]     savepoints: RwLock<std::collections::HashMap<u64, HashMapStoreSnapshot>>,
[INFO] [stdout]     next_savepoint_id: RwLock<u64>,
[INFO] [stdout] }
[INFO] [stdout] impl Default for HashMapStore {
[INFO] [stdout]     fn default() -> Self {
[INFO] [stdout]         Self {
[INFO] [stdout]             folders: RwLock::default(),
[INFO] [stdout]             things: RwLock::default(),
[INFO] [stdout]             tags: RwLock::default(),
[INFO] [stdout]             jn_folder_from_folder_children: RwLock::new(HashMapJunction::exclusive()),
[INFO] [stdout]             jn_tag_from_folder_lead: RwLock::new(HashMapJunction::exclusive()),
[INFO] [stdout]             jn_thing_from_folder_things: RwLock::new(HashMapJunction::exclusive()),
[INFO] [stdout]             jn_tag_from_thing_tags: RwLock::new(HashMapJunction::shared()),
[INFO] [stdout]             counters: RwLock::default(),
[INFO] [stdout]             savepoints: RwLock::default(),
[INFO] [stdout]             next_savepoint_id: RwLock::default(),
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl HashMapStore {
[INFO] [stdout]     pub fn new() -> Self {
[INFO] [stdout]         Self::default()
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Clone the entire store for savepoint support. O(1) thanks to im::HashMap.
[INFO] [stdout]     /// (Poison-tolerant, but NOT atomic across tables — used for savepoints, which
[INFO] [stdout]     /// are taken on the single writer thread with no concurrent writer; use
[INFO] [stdout]     /// [`Self::freeze`] when an atomic cross-table snapshot is required.)
[INFO] [stdout]     pub fn snapshot(&self) -> HashMapStoreSnapshot {
[INFO] [stdout]         HashMapStoreSnapshot {
[INFO] [stdout]             folders: read_or_recover(&self.folders).clone(),
[INFO] [stdout]             things: read_or_recover(&self.things).clone(),
[INFO] [stdout]             tags: read_or_recover(&self.tags).clone(),
[INFO] [stdout]             jn_folder_from_folder_children: read_or_recover(&self.jn_folder_from_folder_children).clone(),
[INFO] [stdout]             jn_tag_from_folder_lead: read_or_recover(&self.jn_tag_from_folder_lead).clone(),
[INFO] [stdout]             jn_thing_from_folder_things: read_or_recover(&self.jn_thing_from_folder_things).clone(),
[INFO] [stdout]             jn_tag_from_thing_tags: read_or_recover(&self.jn_tag_from_thing_tags).clone(),
[INFO] [stdout]             counters: read_or_recover(&self.counters).clone(),
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Atomically snapshot the whole store into a fresh, isolated `HashMapStore`.
[INFO] [stdout]     ///
[INFO] [stdout]     /// Unlike [`Self::snapshot`], this holds a read guard on **every** table, junction,
[INFO] [stdout]     /// and the counters *simultaneously* while cloning, so the result is a single
[INFO] [stdout]     /// consistent point-in-time view of the store *at the instant freeze runs*.
[INFO] [stdout]     /// O(1) clones (im::HashMap structural sharing); the lock hold is just the
[INFO] [stdout]     /// clones. The returned store carries no savepoints and is meant to back a
[INFO] [stdout]     /// read-only (frozen) transaction.
[INFO] [stdout]     ///
[INFO] [stdout]     /// Deadlock-freedom depends on an **invariant** (unenforced by the type
[INFO] [stdout]     /// system): guards are acquired in the struct's declared field order (all
[INFO] [stdout]     /// entity tables, then all junctions, then counters), and no writer ever
[INFO] [stdout]     /// holds two *entity-table* locks simultaneously — each writer takes at most
[INFO] [stdout]     /// one entity table, then its junctions, in table-before-junction order (see
[INFO] [stdout]     /// `remove_multi`). A future cascade that nested two entity-table locks would
[INFO] [stdout]     /// violate this and could deadlock against freeze; keep cascades single-table.
[INFO] [stdout]     ///
[INFO] [stdout]     /// SCOPE: freeze is atomic w.r.t. *itself*, not w.r.t. a writer's *multi-step*
[INFO] [stdout]     /// cascade. A write transaction is not one critical section — its per-repo
[INFO] [stdout]     /// calls each lock/unlock independently — so a cascading delete/create can
[INFO] [stdout]     /// leave a brief window (e.g. child rows removed but the parent's junction not
[INFO] [stdout]     /// yet cleared) into which freeze can land and capture a cross-table-torn
[INFO] [stdout]     /// snapshot (`gather` then hits "entity vanished mid-read", or silently omits
[INFO] [stdout]     /// a just-added-but-unlinked row). This shrinks the torn-read window from the
[INFO] [stdout]     /// whole read to a single instant, but does not eliminate it; a store-wide
[INFO] [stdout]     /// writer/reader gate would be required to fully close it.
[INFO] [stdout]     pub fn freeze(&self) -> HashMapStore {
[INFO] [stdout]         // Acquire ALL read guards first (declared order), then clone under that
[INFO] [stdout]         // one consistent lock set.
[INFO] [stdout]         let g_folders = read_or_recover(&self.folders);
[INFO] [stdout]         let g_things = read_or_recover(&self.things);
[INFO] [stdout]         let g_tags = read_or_recover(&self.tags);
[INFO] [stdout]         let g_jn_folder_from_folder_children = read_or_recover(&self.jn_folder_from_folder_children);
[INFO] [stdout]         let g_jn_tag_from_folder_lead = read_or_recover(&self.jn_tag_from_folder_lead);
[INFO] [stdout]         let g_jn_thing_from_folder_things = read_or_recover(&self.jn_thing_from_folder_things);
[INFO] [stdout]         let g_jn_tag_from_thing_tags = read_or_recover(&self.jn_tag_from_thing_tags);
[INFO] [stdout]         let g_counters = read_or_recover(&self.counters);
[INFO] [stdout] 
[INFO] [stdout]         HashMapStore {
[INFO] [stdout]             folders: RwLock::new(g_folders.clone()),
[INFO] [stdout]             things: RwLock::new(g_things.clone()),
[INFO] [stdout]             tags: RwLock::new(g_tags.clone()),
[INFO] [stdout]             jn_folder_from_folder_children: RwLock::new(g_jn_folder_from_folder_children.clone()),
[INFO] [stdout]             jn_tag_from_folder_lead: RwLock::new(g_jn_tag_from_folder_lead.clone()),
[INFO] [stdout]             jn_thing_from_folder_things: RwLock::new(g_jn_thing_from_folder_things.clone()),
[INFO] [stdout]             jn_tag_from_thing_tags: RwLock::new(g_jn_tag_from_thing_tags.clone()),
[INFO] [stdout]             counters: RwLock::new(g_counters.clone()),
[INFO] [stdout]             savepoints: RwLock::new(std::collections::HashMap::new()),
[INFO] [stdout]             next_savepoint_id: RwLock::new(0),
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Restore from a savepoint snapshot.
[INFO] [stdout]     pub fn restore(&self, snap: &HashMapStoreSnapshot) {
[INFO] [stdout]         *write_or_recover(&self.folders) = snap.folders.clone();
[INFO] [stdout]         *write_or_recover(&self.things) = snap.things.clone();
[INFO] [stdout]         *write_or_recover(&self.tags) = snap.tags.clone();
[INFO] [stdout]         *write_or_recover(&self.jn_folder_from_folder_children) = snap.jn_folder_from_folder_children.clone();
[INFO] [stdout]         *write_or_recover(&self.jn_tag_from_folder_lead) = snap.jn_tag_from_folder_lead.clone();
[INFO] [stdout]         *write_or_recover(&self.jn_thing_from_folder_things) = snap.jn_thing_from_folder_things.clone();
[INFO] [stdout]         *write_or_recover(&self.jn_tag_from_thing_tags) = snap.jn_tag_from_thing_tags.clone();
[INFO] [stdout]         *write_or_recover(&self.counters) = snap.counters.clone();
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Create a savepoint by snapshotting the entire store. Returns a savepoint id.
[INFO] [stdout]     /// O(1) thanks to im::HashMap structural sharing.
[INFO] [stdout]     pub fn create_savepoint(&self) -> u64 {
[INFO] [stdout]         let snap = self.snapshot();
[INFO] [stdout]         let mut id_counter = write_or_recover(&self.next_savepoint_id);
[INFO] [stdout]         let id = *id_counter;
[INFO] [stdout]         *id_counter += 1;
[INFO] [stdout]         write_or_recover(&self.savepoints).insert(id, snap);
[INFO] [stdout]         id
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Restore the store to a previously created savepoint.
[INFO] [stdout]     pub fn restore_savepoint(&self, savepoint_id: u64) {
[INFO] [stdout]         // A missing savepoint is a caller bug, but this is reached from
[INFO] [stdout]         // `Transaction`'s **`Drop`-time rollback** — and panicking in a `Drop`
[INFO] [stdout]         // that is itself running because of an earlier panic aborts the process
[INFO] [stdout]         // outright, no matter what the panic strategy is. That double-panic is
[INFO] [stdout]         // exactly what this module's poison recovery exists to avoid, so the
[INFO] [stdout]         // rollback must not be the thing that reintroduces it.
[INFO] [stdout]         //
[INFO] [stdout]         // Loud in a debug build, a no-op in a shipped one: nothing to restore
[INFO] [stdout]         // to means the store simply stays as it is.
[INFO] [stdout]         let Some(snap) = read_or_recover(&self.savepoints)
[INFO] [stdout]             .get(&savepoint_id)
[INFO] [stdout]             .cloned()
[INFO] [stdout]         else {
[INFO] [stdout]             debug_assert!(
[INFO] [stdout]                 false,
[INFO] [stdout]                 "restore_savepoint: savepoint {savepoint_id} does not exist — it was already \
[INFO] [stdout]                  restored, discarded, or never created"
[INFO] [stdout]             );
[INFO] [stdout]             return;
[INFO] [stdout]         };
[INFO] [stdout]         self.restore(&snap);
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Discard a savepoint without restoring (used on successful commit, and
[INFO] [stdout]     /// after a rollback so a rolled-back transaction doesn't leak its snapshot).
[INFO] [stdout]     pub fn discard_savepoint(&self, savepoint_id: u64) {
[INFO] [stdout]         write_or_recover(&self.savepoints).remove(&savepoint_id);
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Get-and-increment counter for an entity type.
[INFO] [stdout]     pub(crate) fn next_id(&self, entity_name: &str) -> EntityId {
[INFO] [stdout]         let mut counters = write_or_recover(&self.counters);
[INFO] [stdout]         let counter = counters.entry(entity_name.to_string()).or_insert(1);
[INFO] [stdout]         let id = *counter;
[INFO] [stdout]         *counter += 1;
[INFO] [stdout]         id
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Restore entity and junction data but preserve current counters.
[INFO] [stdout]     /// Used for undo snapshots where IDs must remain monotonically increasing.
[INFO] [stdout]     pub fn restore_without_counters(&self, snap: &HashMapStoreSnapshot) {
[INFO] [stdout]         *write_or_recover(&self.folders) = snap.folders.clone();
[INFO] [stdout]         *write_or_recover(&self.things) = snap.things.clone();
[INFO] [stdout]         *write_or_recover(&self.tags) = snap.tags.clone();
[INFO] [stdout]         *write_or_recover(&self.jn_folder_from_folder_children) = snap.jn_folder_from_folder_children.clone();
[INFO] [stdout]         *write_or_recover(&self.jn_tag_from_folder_lead) = snap.jn_tag_from_folder_lead.clone();
[INFO] [stdout]         *write_or_recover(&self.jn_thing_from_folder_things) = snap.jn_thing_from_folder_things.clone();
[INFO] [stdout]         *write_or_recover(&self.jn_tag_from_thing_tags) = snap.jn_tag_from_thing_tags.clone();
[INFO] [stdout]         // counters intentionally NOT restored — IDs must remain monotonically increasing
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Create a type-erased store snapshot for undo.
[INFO] [stdout]     pub fn store_snapshot(&self) -> StoreSnapshot {
[INFO] [stdout]         StoreSnapshot::new(self.snapshot())
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Restore from a type-erased store snapshot (preserves counters).
[INFO] [stdout]     pub fn restore_store_snapshot(&self, snap: &StoreSnapshot) {
[INFO] [stdout]         let s = snap
[INFO] [stdout]             .downcast_ref::<HashMapStoreSnapshot>()
[INFO] [stdout]             .expect("StoreSnapshot must contain HashMapStoreSnapshot");
[INFO] [stdout]         self.restore_without_counters(s);
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Check every junction's reverse index against a rebuild from its rows,
[INFO] [stdout]     /// and that each junction is exclusive exactly when its relationship is.
[INFO] [stdout]     ///
[INFO] [stdout]     /// A diagnostic for tests: it walks every junction, so it costs O(store).
[INFO] [stdout]     /// Nothing calls it on a write path. Returns the first discrepancy found.
[INFO] [stdout]     pub fn check_junction_indexes(&self) -> Result<(), String> {
[INFO] [stdout]         {
[INFO] [stdout]             let jn = read_or_recover(&self.jn_folder_from_folder_children);
[INFO] [stdout]             if !jn.is_exclusive() {
[INFO] [stdout]                 return Err("jn_folder_from_folder_children: exclusivity does not match the relationship".into());
[INFO] [stdout]             }
[INFO] [stdout]             jn.check_index()
[INFO] [stdout]                 .map_err(|e| format!("jn_folder_from_folder_children: {e}"))?;
[INFO] [stdout]         }
[INFO] [stdout]         {
[INFO] [stdout]             let jn = read_or_recover(&self.jn_tag_from_folder_lead);
[INFO] [stdout]             if !jn.is_exclusive() {
[INFO] [stdout]                 return Err("jn_tag_from_folder_lead: exclusivity does not match the relationship".into());
[INFO] [stdout]             }
[INFO] [stdout]             jn.check_index()
[INFO] [stdout]                 .map_err(|e| format!("jn_tag_from_folder_lead: {e}"))?;
[INFO] [stdout]         }
[INFO] [stdout]         {
[INFO] [stdout]             let jn = read_or_recover(&self.jn_thing_from_folder_things);
[INFO] [stdout]             if !jn.is_exclusive() {
[INFO] [stdout]                 return Err("jn_thing_from_folder_things: exclusivity does not match the relationship".into());
[INFO] [stdout]             }
[INFO] [stdout]             jn.check_index()
[INFO] [stdout]                 .map_err(|e| format!("jn_thing_from_folder_things: {e}"))?;
[INFO] [stdout]         }
[INFO] [stdout]         {
[INFO] [stdout]             let jn = read_or_recover(&self.jn_tag_from_thing_tags);
[INFO] [stdout]             if jn.is_exclusive() {
[INFO] [stdout]                 return Err("jn_tag_from_thing_tags: exclusivity does not match the relationship".into());
[INFO] [stdout]             }
[INFO] [stdout]             jn.check_index()
[INFO] [stdout]                 .map_err(|e| format!("jn_tag_from_thing_tags: {e}"))?;
[INFO] [stdout]         }
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] /// Snapshot of the entire store. O(1) to create thanks to im::HashMap structural sharing.
[INFO] [stdout] ///
[INFO] [stdout] /// Entity-table and junction fields are `pub(crate)` so the generated per-entity
[INFO] [stdout] /// repositories (same crate) can read the captured rows/junctions during a *scoped*
[INFO] [stdout] /// restore. Counters stay private (restore never touches them — ids stay monotonic).
[INFO] [stdout] #[derive(Debug, Clone)]
[INFO] [stdout] pub struct HashMapStoreSnapshot {
[INFO] [stdout]     pub(crate) folders: HashMap<EntityId, Folder>,
[INFO] [stdout]     pub(crate) things: HashMap<EntityId, Thing>,
[INFO] [stdout]     pub(crate) tags: HashMap<EntityId, Tag>,
[INFO] [stdout]     pub(crate) jn_folder_from_folder_children: HashMapJunction,
[INFO] [stdout]     pub(crate) jn_tag_from_folder_lead: HashMapJunction,
[INFO] [stdout]     pub(crate) jn_thing_from_folder_things: HashMapJunction,
[INFO] [stdout]     pub(crate) jn_tag_from_thing_tags: HashMapJunction,
[INFO] [stdout]     counters: std::collections::HashMap<String, EntityId>,
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl StoreSnapshotTrait for HashMapStoreSnapshot {
[INFO] [stdout]     fn clone_box(&self) -> Box<dyn StoreSnapshotTrait> {
[INFO] [stdout]         Box::new(self.clone())
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn as_any(&self) -> &dyn std::any::Any {
[INFO] [stdout]         self
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] // ─────────────────────────────────────────────────────────────────────────────
[INFO] [stdout] // Junctions
[INFO] [stdout] // ─────────────────────────────────────────────────────────────────────────────
[INFO] [stdout] 
[INFO] [stdout] /// One relationship's junction: each source id mapped to its ordered target ids.
[INFO] [stdout] ///
[INFO] [stdout] /// The junction of an *exclusive* relationship (one-to-one, one-to-many,
[INFO] [stdout] /// ordered one-to-many) also keeps a reverse index, target to the sources
[INFO] [stdout] /// holding it, so that "this target already belongs to another source" costs
[INFO] [stdout] /// one lookup per target of a write instead of a walk over every row of the
[INFO] [stdout] /// relationship. Without it, creating N rows that each own a child is O(N²).
[INFO] [stdout] ///
[INFO] [stdout] /// The rows are private, so nothing can change them behind the index: every
[INFO] [stdout] /// mutation goes through [`Self::insert`], [`Self::remove`] or
[INFO] [stdout] /// [`Self::remove_target`], which update both in the same call, and a
[INFO] [stdout] /// whole-junction clone (snapshot, savepoint, [`HashMapStore::freeze`],
[INFO] [stdout] /// restore) carries both together. Both are `im::HashMap`s, so that clone
[INFO] [stdout] /// stays O(1).
[INFO] [stdout] ///
[INFO] [stdout] /// The read methods mirror the map it wraps (`get`, `iter`, `contains_key`, …),
[INFO] [stdout] /// so code that only reads a junction reads it as before.
[INFO] [stdout] #[derive(Debug, Clone, Default)]
[INFO] [stdout] pub struct HashMapJunction {
[INFO] [stdout]     rows: HashMap<EntityId, Vec<EntityId>>,
[INFO] [stdout]     /// `None` for a shared relationship (many-to-one, many-to-many), which has
[INFO] [stdout]     /// no ownership rule to check. For an exclusive one, every source whose row
[INFO] [stdout]     /// holds the target, once per occurrence. A consistent store has exactly
[INFO] [stdout]     /// one; keeping a list makes the index exact even when a write that is not
[INFO] [stdout]     /// validated (a scoped undo restoring rows from its snapshot) leaves one
[INFO] [stdout]     /// target in two rows, so every check still answers exactly as a walk over
[INFO] [stdout]     /// the rows would.
[INFO] [stdout]     owners: Option<HashMap<EntityId, Vec<EntityId>>>,
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl HashMapJunction {
[INFO] [stdout]     /// An empty junction for an exclusive relationship, with its reverse index.
[INFO] [stdout]     pub fn exclusive() -> Self {
[INFO] [stdout]         Self {
[INFO] [stdout]             rows: HashMap::new(),
[INFO] [stdout]             owners: Some(HashMap::new()),
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// An empty junction for a shared relationship, without a reverse index.
[INFO] [stdout]     pub fn shared() -> Self {
[INFO] [stdout]         Self::default()
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Whether this junction belongs to an exclusive relationship.
[INFO] [stdout]     pub fn is_exclusive(&self) -> bool {
[INFO] [stdout]         self.owners.is_some()
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// The ordered targets of `source`, if it has a row.
[INFO] [stdout]     pub fn get(&self, source: &EntityId) -> Option<&Vec<EntityId>> {
[INFO] [stdout]         self.rows.get(source)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Whether `source` has a row (possibly empty).
[INFO] [stdout]     pub fn contains_key(&self, source: &EntityId) -> bool {
[INFO] [stdout]         self.rows.contains_key(source)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Every row, as `(source, targets)`, in no particular order.
[INFO] [stdout]     pub fn iter(&self) -> impl Iterator<Item = (&EntityId, &Vec<EntityId>)> {
[INFO] [stdout]         self.rows.iter()
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Every source that has a row, in no particular order.
[INFO] [stdout]     pub fn keys(&self) -> impl Iterator<Item = &EntityId> {
[INFO] [stdout]         self.rows.keys()
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Every row's targets, in no particular order.
[INFO] [stdout]     pub fn values(&self) -> impl Iterator<Item = &Vec<EntityId>> {
[INFO] [stdout]         self.rows.values()
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// The number of rows.
[INFO] [stdout]     pub fn len(&self) -> usize {
[INFO] [stdout]         self.rows.len()
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Whether the junction has no row at all.
[INFO] [stdout]     pub fn is_empty(&self) -> bool {
[INFO] [stdout]         self.rows.is_empty()
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Every source whose row holds `target`, once per occurrence: none or one
[INFO] [stdout]     /// in a consistent exclusive junction. One lookup on an exclusive junction;
[INFO] [stdout]     /// a walk over every row on a shared one, which has no index.
[INFO] [stdout]     pub fn sources_of(&self, target: &EntityId) -> Vec<EntityId> {
[INFO] [stdout]         match &self.owners {
[INFO] [stdout]             Some(owners) => owners.get(target).cloned().unwrap_or_default(),
[INFO] [stdout]             None => self
[INFO] [stdout]                 .rows
[INFO] [stdout]                 .iter()
[INFO] [stdout]                 .flat_map(|(source, targets)| {
[INFO] [stdout]                     targets.iter().filter(|t| *t == target).map(move |_| *source)
[INFO] [stdout]                 })
[INFO] [stdout]                 .collect(),
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Replace the row of `source` with `targets`, returning the previous row.
[INFO] [stdout]     pub fn insert(&mut self, source: EntityId, targets: Vec<EntityId>) -> Option<Vec<EntityId>> {
[INFO] [stdout]         if let Some(owners) = self.owners.as_mut() {
[INFO] [stdout]             let previous = self.rows.get(&source).map(Vec::as_slice).unwrap_or_default();
[INFO] [stdout]             reindex(owners, source, previous, &targets);
[INFO] [stdout]         }
[INFO] [stdout]         self.rows.insert(source, targets)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Remove the row of `source`, returning it.
[INFO] [stdout]     pub fn remove(&mut self, source: &EntityId) -> Option<Vec<EntityId>> {
[INFO] [stdout]         let removed = self.rows.remove(source)?;
[INFO] [stdout]         if let Some(owners) = self.owners.as_mut() {
[INFO] [stdout]             reindex(owners, *source, &removed, &[]);
[INFO] [stdout]         }
[INFO] [stdout]         Some(removed)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Remove `target` from every row holding it, as when the target entity is
[INFO] [stdout]     /// deleted. Visits only those rows on an exclusive junction, every row on a
[INFO] [stdout]     /// shared one.
[INFO] [stdout]     pub fn remove_target(&mut self, target: &EntityId) {
[INFO] [stdout]         let sources: Vec<EntityId> = match self.owners.as_mut() {
[INFO] [stdout]             Some(owners) => {
[INFO] [stdout]                 let mut sources = owners.remove(target).unwrap_or_default();
[INFO] [stdout]                 sources.sort_unstable();
[INFO] [stdout]                 sources.dedup();
[INFO] [stdout]                 sources
[INFO] [stdout]             }
[INFO] [stdout]             None => self
[INFO] [stdout]                 .rows
[INFO] [stdout]                 .iter()
[INFO] [stdout]                 .filter(|(_, targets)| targets.contains(target))
[INFO] [stdout]                 .map(|(source, _)| *source)
[INFO] [stdout]                 .collect(),
[INFO] [stdout]         };
[INFO] [stdout]         for source in sources {
[INFO] [stdout]             if let Some(targets) = self.rows.get_mut(&source) {
[INFO] [stdout]                 targets.retain(|t| t != target);
[INFO] [stdout]             }
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Compare the reverse index with one rebuilt from the rows.
[INFO] [stdout]     ///
[INFO] [stdout]     /// A diagnostic for tests, O(junction): see
[INFO] [stdout]     /// [`HashMapStore::check_junction_indexes`]. A shared junction has no index
[INFO] [stdout]     /// and always passes.
[INFO] [stdout]     pub fn check_index(&self) -> Result<(), String> {
[INFO] [stdout]         let Some(owners) = &self.owners else {
[INFO] [stdout]             return Ok(());
[INFO] [stdout]         };
[INFO] [stdout]         let mut expected: std::collections::HashMap<EntityId, Vec<EntityId>> =
[INFO] [stdout]             std::collections::HashMap::new();
[INFO] [stdout]         for (source, targets) in self.rows.iter() {
[INFO] [stdout]             for target in targets {
[INFO] [stdout]                 expected.entry(*target).or_default().push(*source);
[INFO] [stdout]             }
[INFO] [stdout]         }
[INFO] [stdout]         if expected.len() != owners.len() {
[INFO] [stdout]             return Err(format!(
[INFO] [stdout]                 "the reverse index lists {} targets but the rows hold {}",
[INFO] [stdout]                 owners.len(),
[INFO] [stdout]                 expected.len()
[INFO] [stdout]             ));
[INFO] [stdout]         }
[INFO] [stdout]         for (target, mut holders) in expected {
[INFO] [stdout]             let mut indexed = owners.get(&target).cloned().unwrap_or_default();
[INFO] [stdout]             holders.sort_unstable();
[INFO] [stdout]             indexed.sort_unstable();
[INFO] [stdout]             if holders != indexed {
[INFO] [stdout]                 return Err(format!(
[INFO] [stdout]                     "target {target} is held by {holders:?} but indexed under {indexed:?}"
[INFO] [stdout]                 ));
[INFO] [stdout]             }
[INFO] [stdout]         }
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] /// Move `source`'s entries in a reverse index from its `previous` targets to
[INFO] [stdout] /// its `next` ones. Only the difference is written, counted per occurrence, so
[INFO] [stdout] /// re-saving a long ordered list with one target appended (how a child is
[INFO] [stdout] /// added to its owner) updates one index entry, not one per child.
[INFO] [stdout] fn reindex(
[INFO] [stdout]     owners: &mut HashMap<EntityId, Vec<EntityId>>,
[INFO] [stdout]     source: EntityId,
[INFO] [stdout]     previous: &[EntityId],
[INFO] [stdout]     next: &[EntityId],
[INFO] [stdout] ) {
[INFO] [stdout]     // A new row and a removed one need no difference: the common cases of
[INFO] [stdout]     // creating and deleting a source skip the scratch map below.
[INFO] [stdout]     if previous.is_empty() {
[INFO] [stdout]         for target in next {
[INFO] [stdout]             link(owners, source, *target, 1);
[INFO] [stdout]         }
[INFO] [stdout]         return;
[INFO] [stdout]     }
[INFO] [stdout]     if next.is_empty() {
[INFO] [stdout]         for target in previous {
[INFO] [stdout]             unlink(owners, source, *target, 1);
[INFO] [stdout]         }
[INFO] [stdout]         return;
[INFO] [stdout]     }
[INFO] [stdout]     if previous == next {
[INFO] [stdout]         return;
[INFO] [stdout]     }
[INFO] [stdout]     let mut delta: std::collections::HashMap<EntityId, isize> =
[INFO] [stdout]         std::collections::HashMap::with_capacity(previous.len() + next.len());
[INFO] [stdout]     for target in previous {
[INFO] [stdout]         *delta.entry(*target).or_default() -= 1;
[INFO] [stdout]     }
[INFO] [stdout]     for target in next {
[INFO] [stdout]         *delta.entry(*target).or_default() += 1;
[INFO] [stdout]     }
[INFO] [stdout]     for (target, change) in delta {
[INFO] [stdout]         if change > 0 {
[INFO] [stdout]             link(owners, source, target, change.unsigned_abs());
[INFO] [stdout]         } else if change < 0 {
[INFO] [stdout]             unlink(owners, source, target, change.unsigned_abs());
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] /// Record `count` more occurrences of `target` in the row of `source`.
[INFO] [stdout] fn link(owners: &mut HashMap<EntityId, Vec<EntityId>>, source: EntityId, target: EntityId, count: usize) {
[INFO] [stdout]     owners
[INFO] [stdout]         .entry(target)
[INFO] [stdout]         .or_default()
[INFO] [stdout]         .extend(std::iter::repeat_n(source, count));
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] /// Forget `count` occurrences of `target` in the row of `source`, dropping the
[INFO] [stdout] /// index entry once no row holds the target any more.
[INFO] [stdout] fn unlink(owners: &mut HashMap<EntityId, Vec<EntityId>>, source: EntityId, target: EntityId, count: usize) {
[INFO] [stdout]     let emptied = match owners.get_mut(&target) {
[INFO] [stdout]         Some(holders) => {
[INFO] [stdout]             for _ in 0..count {
[INFO] [stdout]                 if let Some(at) = holders.iter().position(|h| *h == source) {
[INFO] [stdout]                     holders.swap_remove(at);
[INFO] [stdout]                 }
[INFO] [stdout]             }
[INFO] [stdout]             holders.is_empty()
[INFO] [stdout]         }
[INFO] [stdout]         None => false,
[INFO] [stdout]     };
[INFO] [stdout]     if emptied {
[INFO] [stdout]         owners.remove(&target);
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] // ─────────────────────────────────────────────────────────────────────────────
[INFO] [stdout] // Helper functions
[INFO] [stdout] // ─────────────────────────────────────────────────────────────────────────────
[INFO] [stdout] 
[INFO] [stdout] /// Acquire a read guard, recovering the inner value if the lock was poisoned by
[INFO] [stdout] /// a panic elsewhere — and **clearing** the poison so recovery is permanent, not
[INFO] [stdout] /// one-shot (a recovered-but-still-poisoned lock would re-panic on the next plain
[INFO] [stdout] /// `.read()/.write().unwrap()`). Used throughout the store's own snapshot /
[INFO] [stdout] /// savepoint / restore paths so a single table poisoned by an unrelated panic
[INFO] [stdout] /// can't escalate a rollback (the Drop-time `restore_savepoint`) — or the very
[INFO] [stdout] /// next write transaction's `create_savepoint` — into a process-wide abort.
[INFO] [stdout] pub(crate) fn read_or_recover<T>(lock: &RwLock<T>) -> std::sync::RwLockReadGuard<'_, T> {
[INFO] [stdout]     match lock.read() {
[INFO] [stdout]         Ok(guard) => guard,
[INFO] [stdout]         Err(poisoned) => {
[INFO] [stdout]             let guard = poisoned.into_inner();
[INFO] [stdout]             lock.clear_poison();
[INFO] [stdout]             guard
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] /// Write-guard counterpart of [`read_or_recover`] (also clears the poison).
[INFO] [stdout] pub(crate) fn write_or_recover<T>(lock: &RwLock<T>) -> std::sync::RwLockWriteGuard<'_, T> {
[INFO] [stdout]     match lock.write() {
[INFO] [stdout]         Ok(guard) => guard,
[INFO] [stdout]         Err(poisoned) => {
[INFO] [stdout]             let guard = poisoned.into_inner();
[INFO] [stdout]             lock.clear_poison();
[INFO] [stdout]             guard
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] /// Remove `id` from every row of a junction that points at it (the entity `id`
[INFO] [stdout] /// is being deleted). See [`HashMapJunction::remove_target`].
[INFO] [stdout] pub(crate) fn delete_from_backward_junction(junction: &RwLock<HashMapJunction>, id: &EntityId) {
[INFO] [stdout]     write_or_recover(junction).remove_target(id);
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] pub(crate) fn junction_get(junction: &RwLock<HashMapJunction>, id: &EntityId) -> Vec<EntityId> {
[INFO] [stdout]     read_or_recover(junction)
[INFO] [stdout]         .get(id)
[INFO] [stdout]         .cloned()
[INFO] [stdout]         .unwrap_or_default()
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] pub(crate) fn junction_set(junction: &RwLock<HashMapJunction>, id: EntityId, ids: Vec<EntityId>) {
[INFO] [stdout]     write_or_recover(junction).insert(id, ids);
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] /// Validate a complete replacement batch before changing any junction rows.
[INFO] [stdout] /// Ownership exclusivity is per relationship, independently of cascade strength,
[INFO] [stdout] /// and belongs to the junction itself (see [`HashMapJunction::exclusive`]).
[INFO] [stdout] ///
[INFO] [stdout] /// The cost follows the batch, never the junction: an exclusive target is
[INFO] [stdout] /// looked up in the reverse index, so a batch of k targets costs O(k) however
[INFO] [stdout] /// many rows the relationship holds, and a batch carrying no target at all
[INFO] [stdout] /// does not even take the junction's lock.
[INFO] [stdout] pub(crate) fn validate_junction_updates(
[INFO] [stdout]     junction: &RwLock<HashMapJunction>,
[INFO] [stdout]     replacements: &[(EntityId, Vec<EntityId>)],
[INFO] [stdout]     scalar: bool,
[INFO] [stdout] ) -> Result<(), crate::error::RepositoryError> {
[INFO] [stdout]     use std::collections::HashSet;
[INFO] [stdout]     use crate::error::RepositoryError::ConstraintViolation;
[INFO] [stdout]     let mut owners = HashSet::new();
[INFO] [stdout]     for (owner, targets) in replacements {
[INFO] [stdout]         if !owners.insert(*owner) {
[INFO] [stdout]             return Err(ConstraintViolation("duplicate source in relationship batch".into()));
[INFO] [stdout]         }
[INFO] [stdout]         if scalar && targets.len() > 1 {
[INFO] [stdout]             return Err(ConstraintViolation("to-one relationship accepts at most one target".into()));
[INFO] [stdout]         }
[INFO] [stdout]         let mut unique = HashSet::new();
[INFO] [stdout]         if targets.iter().any(|id| !unique.insert(*id)) {
[INFO] [stdout]             return Err(ConstraintViolation("duplicate relationship target".into()));
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout]     if replacements.iter().all(|(_, targets)| targets.is_empty()) {
[INFO] [stdout]         return Ok(());
[INFO] [stdout]     }
[INFO] [stdout]     let current = read_or_recover(junction);
[INFO] [stdout]     let Some(index) = current.owners.as_ref() else {
[INFO] [stdout]         // A shared relationship: any number of sources may hold a target.
[INFO] [stdout]         return Ok(());
[INFO] [stdout]     };
[INFO] [stdout]     // A target is taken when a source outside this batch holds it, or when two
[INFO] [stdout]     // rows of the batch both claim it. Sources in the batch are being replaced,
[INFO] [stdout]     // so moving a target between two of them within one batch is valid.
[INFO] [stdout]     let mut claimed = HashSet::new();
[INFO] [stdout]     for (_, targets) in replacements {
[INFO] [stdout]         for id in targets {
[INFO] [stdout]             let held_outside = index
[INFO] [stdout]                 .get(id)
[INFO] [stdout]                 .is_some_and(|holders| holders.iter().any(|holder| !owners.contains(holder)));
[INFO] [stdout]             if held_outside || !claimed.insert(*id) {
[INFO] [stdout]                 return Err(ConstraintViolation("relationship target already belongs to another source".into()));
[INFO] [stdout]             }
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout]     Ok(())
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] pub(crate) fn junction_remove(junction: &RwLock<HashMapJunction>, id: &EntityId) {
[INFO] [stdout]     write_or_recover(junction).remove(id);
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] pub(crate) fn junction_get_relationships_from_right_ids(
[INFO] [stdout]     junction: &RwLock<HashMapJunction>,
[INFO] [stdout]     right_ids: &[EntityId],
[INFO] [stdout] ) -> Vec<(EntityId, Vec<EntityId>)> {
[INFO] [stdout]     let jn = read_or_recover(junction);
[INFO] [stdout]     jn.iter()
[INFO] [stdout]         .filter(|(_, rids)| right_ids.iter().any(|eid| rids.contains(eid)))
[INFO] [stdout]         .map(|(left_id, rids)| (*left_id, rids.clone()))
[INFO] [stdout]         .collect()
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] pub(crate) fn junction_move_ids(
[INFO] [stdout]     junction: &RwLock<HashMapJunction>,
[INFO] [stdout]     id: &EntityId,
[INFO] [stdout]     ids_to_move: &[EntityId],
[INFO] [stdout]     new_index: i32,
[INFO] [stdout] ) -> Vec<EntityId> {
[INFO] [stdout]     let current = junction_get(junction, id);
[INFO] [stdout]     if ids_to_move.is_empty() {
[INFO] [stdout]         return current;
[INFO] [stdout]     }
[INFO] [stdout]     let move_set: std::collections::HashSet<EntityId> = ids_to_move.iter().copied().collect();
[INFO] [stdout]     let mut remaining: Vec<EntityId> = current
[INFO] [stdout]         .into_iter()
[INFO] [stdout]         .filter(|eid| !move_set.contains(eid))
[INFO] [stdout]         .collect();
[INFO] [stdout]     let insert_pos = if new_index < 0 || (new_index as usize) > remaining.len() {
[INFO] [stdout]         remaining.len()
[INFO] [stdout]     } else {
[INFO] [stdout]         new_index as usize
[INFO] [stdout]     };
[INFO] [stdout]     for (i, &eid) in ids_to_move.iter().enumerate() {
[INFO] [stdout]         remaining.insert(insert_pos + i, eid);
[INFO] [stdout]     }
[INFO] [stdout]     junction_set(junction, *id, remaining.clone());
[INFO] [stdout]     remaining
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] /// Surgically reconcile an external (out-of-scope) junction list during a scoped undo restore.
[INFO] [stdout] ///
[INFO] [stdout] /// `live_list` is the current ordered value of an external referrer's junction row; `snap_list`
[INFO] [stdout] /// is the same row at snapshot time; `scope` is the set of subtree ids being restored. Only the
[INFO] [stdout] /// membership/position of `scope` ids is reconciled to match the snapshot — non-scope siblings are
[INFO] [stdout] /// preserved in their current relative order. Scope ids absent from the snapshot are removed
[INFO] [stdout] /// (undo-of-create); scope ids present in the snapshot are re-inserted at their snapshot-relative
[INFO] [stdout] /// position (undo-of-remove). This is what keeps undo from clobbering other trunks' edits.
[INFO] [stdout] pub(crate) fn reconcile_backref_list(
[INFO] [stdout]     live_list: &[EntityId],
[INFO] [stdout]     snap_list: &[EntityId],
[INFO] [stdout]     scope: &std::collections::HashSet<EntityId>,
[INFO] [stdout] ) -> Vec<EntityId> {
[INFO] [stdout]     // Scope ids that should be present after restore, in snapshot order.
[INFO] [stdout]     let snap_members: Vec<EntityId> =
[INFO] [stdout]         snap_list.iter().copied().filter(|x| scope.contains(x)).collect();
[INFO] [stdout]     let snap_member_set: std::collections::HashSet<EntityId> =
[INFO] [stdout]         snap_members.iter().copied().collect();
[INFO] [stdout] 
[INFO] [stdout]     // Start from the live list, dropping only scope ids that are NOT in the snapshot.
[INFO] [stdout]     let mut work: Vec<EntityId> = live_list
[INFO] [stdout]         .iter()
[INFO] [stdout]         .copied()
[INFO] [stdout]         // De Morgan'd from `!(in_scope && !in_snapshot)`: same rule, stated the
[INFO] [stdout]         // way clippy's `nonminimal_bool` wants it, and arguably plainer — keep
[INFO] [stdout]         // anything outside the scope, plus anything the snapshot still holds.
[INFO] [stdout]         .filter(|x| !scope.contains(x) || snap_member_set.contains(x))
[INFO] [stdout]         .collect();
[INFO] [stdout] 
[INFO] [stdout]     // Re-insert missing scope ids at their snapshot-relative position (count of snapshot
[INFO] [stdout]     // predecessors that survive in `work`), so they return next to their original neighbours.
[INFO] [stdout]     for id in &snap_members {
[INFO] [stdout]         if work.contains(id) {
[INFO] [stdout]             continue;
[INFO] [stdout]         }
[INFO] [stdout]         let snap_idx = snap_list.iter().position(|x| x == id).unwrap_or(0);
[INFO] [stdout]         let pos = snap_list[..snap_idx]
[INFO] [stdout]             .iter()
[INFO] [stdout]             .filter(|x| work.contains(x))
[INFO] [stdout]             .count()
[INFO] [stdout]             .min(work.len());
[INFO] [stdout]         work.insert(pos, *id);
[INFO] [stdout]     }
[INFO] [stdout]     work
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] // ═════════════════════════════════════════════════════════════════════════════
[INFO] [stdout] // Macros for entities WITH relationships (reduces boilerplate)
[INFO] [stdout] // ═════════════════════════════════════════════════════════════════════════════
[INFO] [stdout] 
[INFO] [stdout] /// Generate standard relationship methods for a HashMap table implementation.
[INFO] [stdout] #[macro_export]
[INFO] [stdout] macro_rules! impl_relationship_methods {
[INFO] [stdout]     ($table_type:ty, $field_enum:ty) => {
[INFO] [stdout]         fn get_relationship(
[INFO] [stdout]             &self,
[INFO] [stdout]             id: &$crate::types::EntityId,
[INFO] [stdout]             field: &$field_enum,
[INFO] [stdout]         ) -> Result<Vec<$crate::types::EntityId>, $crate::error::RepositoryError> {
[INFO] [stdout]             Ok($crate::database::hashmap_store::junction_get(
[INFO] [stdout]                 self.resolve_junction(field),
[INFO] [stdout]                 id,
[INFO] [stdout]             ))
[INFO] [stdout]         }
[INFO] [stdout] 
[INFO] [stdout]         fn get_relationship_many(
[INFO] [stdout]             &self,
[INFO] [stdout]             ids: &[$crate::types::EntityId],
[INFO] [stdout]             field: &$field_enum,
[INFO] [stdout]         ) -> Result<
[INFO] [stdout]             std::collections::HashMap<$crate::types::EntityId, Vec<$crate::types::EntityId>>,
[INFO] [stdout]             $crate::error::RepositoryError,
[INFO] [stdout]         > {
[INFO] [stdout]             let jn = self.resolve_junction(field);
[INFO] [stdout]             let mut map = std::collections::HashMap::new();
[INFO] [stdout]             for id in ids {
[INFO] [stdout]                 map.insert(*id, $crate::database::hashmap_store::junction_get(jn, id));
[INFO] [stdout]             }
[INFO] [stdout]             Ok(map)
[INFO] [stdout]         }
[INFO] [stdout] 
[INFO] [stdout]         fn get_relationship_count(
[INFO] [stdout]             &self,
[INFO] [stdout]             id: &$crate::types::EntityId,
[INFO] [stdout]             field: &$field_enum,
[INFO] [stdout]         ) -> Result<usize, $crate::error::RepositoryError> {
[INFO] [stdout]             Ok(
[INFO] [stdout]                 $crate::database::hashmap_store::junction_get(self.resolve_junction(field), id)
[INFO] [stdout]                     .len(),
[INFO] [stdout]             )
[INFO] [stdout]         }
[INFO] [stdout] 
[INFO] [stdout]         fn get_relationship_in_range(
[INFO] [stdout]             &self,
[INFO] [stdout]             id: &$crate::types::EntityId,
[INFO] [stdout]             field: &$field_enum,
[INFO] [stdout]             offset: usize,
[INFO] [stdout]             limit: usize,
[INFO] [stdout]         ) -> Result<Vec<$crate::types::EntityId>, $crate::error::RepositoryError> {
[INFO] [stdout]             let all =
[INFO] [stdout]                 $crate::database::hashmap_store::junction_get(self.resolve_junction(field), id);
[INFO] [stdout]             Ok(all.into_iter().skip(offset).take(limit).collect())
[INFO] [stdout]         }
[INFO] [stdout] 
[INFO] [stdout]         fn get_relationships_from_right_ids(
[INFO] [stdout]             &self,
[INFO] [stdout]             field: &$field_enum,
[INFO] [stdout]             right_ids: &[$crate::types::EntityId],
[INFO] [stdout]         ) -> Result<
[INFO] [stdout]             Vec<($crate::types::EntityId, Vec<$crate::types::EntityId>)>,
[INFO] [stdout]             $crate::error::RepositoryError,
[INFO] [stdout]         > {
[INFO] [stdout]             Ok(
[INFO] [stdout]                 $crate::database::hashmap_store::junction_get_relationships_from_right_ids(
[INFO] [stdout]                     self.resolve_junction(field),
[INFO] [stdout]                     right_ids,
[INFO] [stdout]                 ),
[INFO] [stdout]             )
[INFO] [stdout]         }
[INFO] [stdout]     };
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] #[macro_export]
[INFO] [stdout] macro_rules! impl_write_relationship_methods {
[INFO] [stdout]     ($table_type:ty, $field_enum:ty) => {
[INFO] [stdout]         $crate::impl_relationship_methods!($table_type, $field_enum);
[INFO] [stdout] 
[INFO] [stdout]         fn set_relationship_multi(
[INFO] [stdout]             &mut self,
[INFO] [stdout]             field: &$field_enum,
[INFO] [stdout]             relationships: Vec<($crate::types::EntityId, Vec<$crate::types::EntityId>)>,
[INFO] [stdout]         ) -> Result<(), $crate::error::RepositoryError> {
[INFO] [stdout]             for (id, _) in &relationships {
[INFO] [stdout]                 if !self.relationship_source_exists(id) {
[INFO] [stdout]                     return Err($crate::error::RepositoryError::ConstraintViolation("relationship source does not exist".into()));
[INFO] [stdout]                 }
[INFO] [stdout]             }
[INFO] [stdout]             self.validate_relationship_updates(field, &relationships, &[])?;
[INFO] [stdout]             let jn = self.resolve_junction(field);
[INFO] [stdout]             for (left_id, entities) in relationships {
[INFO] [stdout]                 $crate::database::hashmap_store::junction_set(jn, left_id, entities);
[INFO] [stdout]             }
[INFO] [stdout]             Ok(())
[INFO] [stdout]         }
[INFO] [stdout] 
[INFO] [stdout]         fn set_relationship(
[INFO] [stdout]             &mut self,
[INFO] [stdout]             id: &$crate::types::EntityId,
[INFO] [stdout]             field: &$field_enum,
[INFO] [stdout]             right_ids: &[$crate::types::EntityId],
[INFO] [stdout]         ) -> Result<(), $crate::error::RepositoryError> {
[INFO] [stdout]             self.set_relationship_multi(field, vec![(*id, right_ids.to_vec())])
[INFO] [stdout]         }
[INFO] [stdout] 
[INFO] [stdout]         fn move_relationship_ids(
[INFO] [stdout]             &mut self,
[INFO] [stdout]             id: &$crate::types::EntityId,
[INFO] [stdout]             field: &$field_enum,
[INFO] [stdout]             ids_to_move: &[$crate::types::EntityId],
[INFO] [stdout]             new_index: i32,
[INFO] [stdout]         ) -> Result<Vec<$crate::types::EntityId>, $crate::error::RepositoryError> {
[INFO] [stdout]             if !self.relationship_is_ordered(field) {
[INFO] [stdout]                 return Err($crate::error::RepositoryError::ConstraintViolation("only ordered relationships can be moved".into()));
[INFO] [stdout]             }
[INFO] [stdout]             let current = $crate::database::hashmap_store::junction_get(self.resolve_junction(field), id);
[INFO] [stdout]             let mut seen = std::collections::HashSet::new();
[INFO] [stdout]             if ids_to_move.iter().any(|target| !seen.insert(*target) || !current.contains(target)) {
[INFO] [stdout]                 return Err($crate::error::RepositoryError::ConstraintViolation("move requires distinct existing relationship members".into()));
[INFO] [stdout]             }
[INFO] [stdout]             self.validate_relationship_updates(field, &[(*id, current)], &[])?;
[INFO] [stdout]             if !self.relationship_source_exists(id) {
[INFO] [stdout]                 return Err($crate::error::RepositoryError::ConstraintViolation("relationship source does not exist".into()));
[INFO] [stdout]             }
[INFO] [stdout]             Ok($crate::database::hashmap_store::junction_move_ids(
[INFO] [stdout]                 self.resolve_junction(field),
[INFO] [stdout]                 id,
[INFO] [stdout]                 ids_to_move,
[INFO] [stdout]                 new_index,
[INFO] [stdout]             ))
[INFO] [stdout]         }
[INFO] [stdout]     };
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] // ═════════════════════════════════════════════════════════════════════════════
[INFO] [stdout] // Leaf entity macro (entities without forward relationships)
[INFO] [stdout] // ═════════════════════════════════════════════════════════════════════════════
[INFO] [stdout] 
[INFO] [stdout] #[macro_export]
[INFO] [stdout] macro_rules! impl_leaf_entity_table {
[INFO] [stdout]     (
[INFO] [stdout]         entity: $Entity:ident,
[INFO] [stdout]         entity_name: $entity_name:expr,
[INFO] [stdout]         store_field: $store_field:ident,
[INFO] [stdout]         table_trait: $TableTrait:ident,
[INFO] [stdout]         table_ro_trait: $TableROTrait:ident,
[INFO] [stdout]         table_struct: $TableStruct:ident,
[INFO] [stdout]         table_ro_struct: $TableROStruct:ident,
[INFO] [stdout]         backward_junctions: [ $( ($bj_field:ident) ),* $(,)? ],
[INFO] [stdout]     ) => {
[INFO] [stdout]         pub struct $TableStruct<'a> {
[INFO] [stdout]             store: &'a $crate::database::hashmap_store::HashMapStore,
[INFO] [stdout]         }
[INFO] [stdout] 
[INFO] [stdout]         impl<'a> $TableStruct<'a> {
[INFO] [stdout]             pub fn new(store: &'a $crate::database::hashmap_store::HashMapStore) -> Self {
[INFO] [stdout]                 Self { store }
[INFO] [stdout]             }
[INFO] [stdout]         }
[INFO] [stdout] 
[INFO] [stdout]         impl<'a> $TableTrait for $TableStruct<'a> {
[INFO] [stdout]             fn create(&mut self, entity: &$Entity) -> Result<$Entity, $crate::error::RepositoryError> {
[INFO] [stdout]                 self.create_multi(std::slice::from_ref(entity))
[INFO] [stdout]                     .and_then(|v| {
[INFO] [stdout]                         v.into_iter().next().ok_or_else(|| {
[INFO] [stdout]                             $crate::error::RepositoryError::Other(::anyhow::anyhow!(
[INFO] [stdout]                                 "create: the store returned no row for the entity it was given"
[INFO] [stdout]                             ))
[INFO] [stdout]                         })
[INFO] [stdout]                     })
[INFO] [stdout]             }
[INFO] [stdout] 
[INFO] [stdout]             fn create_multi(&mut self, entities: &[$Entity]) -> Result<Vec<$Entity>, $crate::error::RepositoryError> {
[INFO] [stdout]                 let mut created = Vec::with_capacity(entities.len());
[INFO] [stdout]                 let mut map = $crate::database::hashmap_store::write_or_recover(&self.store.$store_field);
[INFO] [stdout] 
[INFO] [stdout]                 for entity in entities {
[INFO] [stdout]                     let new_entity = if entity.id == $crate::types::EntityId::default() {
[INFO] [stdout]                         let id = self.store.next_id($entity_name);
[INFO] [stdout]                         $Entity {
[INFO] [stdout]                             id,
[INFO] [stdout]                             ..entity.clone()
[INFO] [stdout]                         }
[INFO] [stdout]                     } else {
[INFO] [stdout]                         if map.contains_key(&entity.id) {
[INFO] [stdout]                             return Err($crate::error::RepositoryError::DuplicateId {
[INFO] [stdout]                                 entity: stringify!($Entity),
[INFO] [stdout]                                 id: entity.id,
[INFO] [stdout]                             });
[INFO] [stdout]                         }
[INFO] [stdout]                         entity.clone()
[INFO] [stdout]                     };
[INFO] [stdout] 
[INFO] [stdout]                     map.insert(new_entity.id, new_entity.clone());
[INFO] [stdout]                     created.push(new_entity);
[INFO] [stdout]                 }
[INFO] [stdout]                 Ok(created)
[INFO] [stdout]             }
[INFO] [stdout] 
[INFO] [stdout]             fn get(&self, id: &$crate::types::EntityId) -> Result<Option<$Entity>, $crate::error::RepositoryError> {
[INFO] [stdout]                 Ok($crate::database::hashmap_store::read_or_recover(&self.store.$store_field).get(id).cloned())
[INFO] [stdout]             }
[INFO] [stdout] 
[INFO] [stdout]             fn get_multi(&self, ids: &[$crate::types::EntityId]) -> Result<Vec<Option<$Entity>>, $crate::error::RepositoryError> {
[INFO] [stdout]                 let map = $crate::database::hashmap_store::read_or_recover(&self.store.$store_field);
[INFO] [stdout]                 Ok(ids.iter().map(|id| map.get(id).cloned()).collect())
[INFO] [stdout]             }
[INFO] [stdout] 
[INFO] [stdout]             fn get_all(&self) -> Result<Vec<$Entity>, $crate::error::RepositoryError> {
[INFO] [stdout]                 Ok($crate::database::hashmap_store::read_or_recover(&self.store.$store_field).values().cloned().collect())
[INFO] [stdout]             }
[INFO] [stdout] 
[INFO] [stdout]             fn update(&mut self, entity: &$Entity) -> Result<$Entity, $crate::error::RepositoryError> {
[INFO] [stdout]                 self.update_multi(std::slice::from_ref(entity))
[INFO] [stdout]                     .and_then(|v| {
[INFO] [stdout]                         v.into_iter().next().ok_or_else(|| {
[INFO] [stdout]                             $crate::error::RepositoryError::Other(::anyhow::anyhow!(
[INFO] [stdout]                                 "update: the store returned no row for the entity it was given"
[INFO] [stdout]                             ))
[INFO] [stdout]                         })
[INFO] [stdout]                     })
[INFO] [stdout]             }
[INFO] [stdout] 
[INFO] [stdout]             fn update_multi(&mut self, entities: &[$Entity]) -> Result<Vec<$Entity>, $crate::error::RepositoryError> {
[INFO] [stdout]                 let mut map = $crate::database::hashmap_store::write_or_recover(&self.store.$store_field);
[INFO] [stdout]                 let mut result = Vec::with_capacity(entities.len());
[INFO] [stdout]                 for entity in entities {
[INFO] [stdout]                     map.insert(entity.id, entity.clone());
[INFO] [stdout]                     result.push(entity.clone());
[INFO] [stdout]                 }
[INFO] [stdout]                 Ok(result)
[INFO] [stdout]             }
[INFO] [stdout] 
[INFO] [stdout]             fn update_with_relationships(&mut self, entity: &$Entity) -> Result<$Entity, $crate::error::RepositoryError> {
[INFO] [stdout]                 self.update(entity)
[INFO] [stdout]             }
[INFO] [stdout] 
[INFO] [stdout]             fn update_with_relationships_multi(&mut self, entities: &[$Entity]) -> Result<Vec<$Entity>, $crate::error::RepositoryError> {
[INFO] [stdout]                 self.update_multi(entities)
[INFO] [stdout]             }
[INFO] [stdout] 
[INFO] [stdout]             fn remove(&mut self, id: &$crate::types::EntityId) -> Result<(), $crate::error::RepositoryError> {
[INFO] [stdout]                 self.remove_multi(std::slice::from_ref(id))
[INFO] [stdout]             }
[INFO] [stdout] 
[INFO] [stdout]             fn remove_multi(&mut self, ids: &[$crate::types::EntityId]) -> Result<(), $crate::error::RepositoryError> {
[INFO] [stdout]                 let mut map = $crate::database::hashmap_store::write_or_recover(&self.store.$store_field);
[INFO] [stdout]                 for id in ids {
[INFO] [stdout]                     map.remove(id);
[INFO] [stdout]                     $(
[INFO] [stdout]                         $crate::database::hashmap_store::delete_from_backward_junction(&self.store.$bj_field, id);
[INFO] [stdout]                     )*
[INFO] [stdout]                 }
[INFO] [stdout]                 Ok(())
[INFO] [stdout]             }
[INFO] [stdout]         }
[INFO] [stdout] 
[INFO] [stdout]         pub struct $TableROStruct<'a> {
[INFO] [stdout]             store: &'a $crate::database::hashmap_store::HashMapStore,
[INFO] [stdout]         }
[INFO] [stdout] 
[INFO] [stdout]         impl<'a> $TableROStruct<'a> {
[INFO] [stdout]             pub fn new(store: &'a $crate::database::hashmap_store::HashMapStore) -> Self {
[INFO] [stdout]                 Self { store }
[INFO] [stdout]             }
[INFO] [stdout]         }
[INFO] [stdout] 
[INFO] [stdout]         impl<'a> $TableROTrait for $TableROStruct<'a> {
[INFO] [stdout]             fn get(&self, id: &$crate::types::EntityId) -> Result<Option<$Entity>, $crate::error::RepositoryError> {
[INFO] [stdout]                 Ok($crate::database::hashmap_store::read_or_recover(&self.store.$store_field).get(id).cloned())
[INFO] [stdout]             }
[INFO] [stdout] 
[INFO] [stdout]             fn get_multi(&self, ids: &[$crate::types::EntityId]) -> Result<Vec<Option<$Entity>>, $crate::error::RepositoryError> {
[INFO] [stdout]                 let map = $crate::database::hashmap_store::read_or_recover(&self.store.$store_field);
[INFO] [stdout]                 Ok(ids.iter().map(|id| map.get(id).cloned()).collect())
[INFO] [stdout]             }
[INFO] [stdout] 
[INFO] [stdout]             fn get_all(&self) -> Result<Vec<$Entity>, $crate::error::RepositoryError> {
[INFO] [stdout]                 Ok($crate::database::hashmap_store::read_or_recover(&self.store.$store_field).values().cloned().collect())
[INFO] [stdout]             }
[INFO] [stdout]         }
[INFO] [stdout]     };
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] // ═════════════════════════════════════════════════════════════════════════════
[INFO] [stdout] // Junction index tests (entity-independent, so generated into every project)
[INFO] [stdout] // ═════════════════════════════════════════════════════════════════════════════
[INFO] [stdout] 
[INFO] [stdout] #[cfg(test)]
[INFO] [stdout] mod junction_tests {
[INFO] [stdout]     use super::*;
[INFO] [stdout]     use crate::error::RepositoryError;
[INFO] [stdout] 
[INFO] [stdout]     /// The 2.0.0 check, kept verbatim as the reference: it walks every row of
[INFO] [stdout]     /// the junction that is not being replaced. The indexed check must give
[INFO] [stdout]     /// the same answer for every junction and every batch.
[INFO] [stdout]     fn validate_by_walking(
[INFO] [stdout]         junction: &HashMapJunction,
[INFO] [stdout]         replacements: &[(EntityId, Vec<EntityId>)],
[INFO] [stdout]         scalar: bool,
[INFO] [stdout]     ) -> Result<(), RepositoryError> {
[INFO] [stdout]         use std::collections::HashSet;
[INFO] [stdout]         use RepositoryError::ConstraintViolation;
[INFO] [stdout]         let mut owners = HashSet::new();
[INFO] [stdout]         for (owner, targets) in replacements {
[INFO] [stdout]             if !owners.insert(*owner) {
[INFO] [stdout]                 return Err(ConstraintViolation("duplicate source in relationship batch".into()));
[INFO] [stdout]             }
[INFO] [stdout]             if scalar && targets.len() > 1 {
[INFO] [stdout]                 return Err(ConstraintViolation("to-one relationship accepts at most one target".into()));
[INFO] [stdout]             }
[INFO] [stdout]             let mut unique = HashSet::new();
[INFO] [stdout]             if targets.iter().any(|id| !unique.insert(*id)) {
[INFO] [stdout]                 return Err(ConstraintViolation("duplicate relationship target".into()));
[INFO] [stdout]             }
[INFO] [stdout]         }
[INFO] [stdout]         if junction.is_exclusive() {
[INFO] [stdout]             let mut used = HashSet::new();
[INFO] [stdout]             for (_, targets) in junction.iter().filter(|(id, _)| !owners.contains(id)) {
[INFO] [stdout]                 used.extend(targets.iter().copied());
[INFO] [stdout]             }
[INFO] [stdout]             for (_, targets) in replacements {
[INFO] [stdout]                 if targets.iter().any(|id| !used.insert(*id)) {
[INFO] [stdout]                     return Err(ConstraintViolation("relationship target already belongs to another source".into()));
[INFO] [stdout]                 }
[INFO] [stdout]             }
[INFO] [stdout]         }
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// xorshift64: deterministic, so a failure replays exactly.
[INFO] [stdout]     struct Rng(u64);
[INFO] [stdout] 
[INFO] [stdout]     impl Rng {
[INFO] [stdout]         fn below(&mut self, bound: u64) -> u64 {
[INFO] [stdout]             self.0 ^= self.0 << 13;
[INFO] [stdout]             self.0 ^= self.0 >> 7;
[INFO] [stdout]             self.0 ^= self.0 << 17;
[INFO] [stdout]             self.0 % bound
[INFO] [stdout]         }
[INFO] [stdout] 
[INFO] [stdout]         fn ids(&mut self, max_len: u64, bound: u64) -> Vec<EntityId> {
[INFO] [stdout]             (0..self.below(max_len + 1)).map(|_| self.below(bound)).collect()
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Drive every writer at random, including the unvalidated ones a scoped
[INFO] [stdout]     /// undo uses, so rows may share a target or repeat one. After each step the
[INFO] [stdout]     /// index must equal a rebuild from the rows, and the indexed check must
[INFO] [stdout]     /// answer a random batch exactly as the walk does.
[INFO] [stdout]     #[test]
[INFO] [stdout]     fn the_index_stays_exact_and_the_check_matches_the_walk() {
[INFO] [stdout]         for exclusive in [true, false] {
[INFO] [stdout]             let mut rng = Rng(0x9E37_79B9_7F4A_7C15 + u64::from(exclusive));
[INFO] [stdout]             let junction = RwLock::new(if exclusive {
[INFO] [stdout]                 HashMapJunction::exclusive()
[INFO] [stdout]             } else {
[INFO] [stdout]                 HashMapJunction::shared()
[INFO] [stdout]             });
[INFO] [stdout]             for step in 0..3000 {
[INFO] [stdout]                 {
[INFO] [stdout]                     let mut jn = write_or_recover(&junction);
[INFO] [stdout]                     match rng.below(6) {
[INFO] [stdout]                         0..=2 => {
[INFO] [stdout]                             let (source, targets) = (rng.below(12), rng.ids(4, 16));
[INFO] [stdout]                             jn.insert(source, targets);
[INFO] [stdout]                         }
[INFO] [stdout]                         3 => {
[INFO] [stdout]                             jn.remove(&rng.below(12));
[INFO] [stdout]                         }
[INFO] [stdout]                         4 => jn.remove_target(&rng.below(16)),
[INFO] [stdout]                         // What snapshot, savepoint and restore do: swap in a clone.
[INFO] [stdout]                         _ => {
[INFO] [stdout]                             let copy = jn.clone();
[INFO] [stdout]                             *jn = copy;
[INFO] [stdout]                         }
[INFO] [stdout]                     }
[INFO] [stdout]                     if let Err(problem) = jn.check_index() {
[INFO] [stdout]                         panic!("exclusive={exclusive}, step {step}: {problem}");
[INFO] [stdout]                     }
[INFO] [stdout]                 }
[INFO] [stdout]                 let batch: Vec<(EntityId, Vec<EntityId>)> = (0..=rng.below(3))
[INFO] [stdout]                     .map(|_| (rng.below(12), rng.ids(2, 16)))
[INFO] [stdout]                     .collect();
[INFO] [stdout]                 for scalar in [false, true] {
[INFO] [stdout]                     let walked = validate_by_walking(&read_or_recover(&junction), &batch, scalar);
[INFO] [stdout]                     let indexed = validate_junction_updates(&junction, &batch, scalar);
[INFO] [stdout]                     assert_eq!(
[INFO] [stdout]                         format!("{indexed:?}"),
[INFO] [stdout]                         format!("{walked:?}"),
[INFO] [stdout]                         "exclusive={exclusive}, step {step}, scalar={scalar}, batch {batch:?}"
[INFO] [stdout]                     );
[INFO] [stdout]                 }
[INFO] [stdout]             }
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     #[test]
[INFO] [stdout]     fn a_transfer_within_one_batch_is_valid_but_not_from_outside_it() {
[INFO] [stdout]         let junction = RwLock::new(HashMapJunction::exclusive());
[INFO] [stdout]         junction_set(&junction, 1, vec![10, 11]);
[INFO] [stdout]         junction_set(&junction, 2, vec![20]);
[INFO] [stdout]         let check = |batch: &[(EntityId, Vec<EntityId>)]| validate_junction_updates(&junction, batch, false);
[INFO] [stdout]         assert!(check(&[(1, vec![11]), (2, vec![20, 10])]).is_ok());
[INFO] [stdout]         assert!(check(&[(2, vec![20, 10])]).is_err());
[INFO] [stdout]         assert!(check(&[(3, vec![10])]).is_err());
[INFO] [stdout]         assert!(check(&[(1, vec![]), (2, vec![10]), (3, vec![10])]).is_err());
[INFO] [stdout]         // Nothing to place: valid however full the junction is.
[INFO] [stdout]         assert!(check(&[(3, vec![]), (4, vec![])]).is_ok());
[INFO] [stdout]         // Deleting the owner, or the target, frees the target.
[INFO] [stdout]         junction_remove(&junction, &1);
[INFO] [stdout]         assert!(check(&[(3, vec![10])]).is_ok());
[INFO] [stdout]         delete_from_backward_junction(&junction, &20);
[INFO] [stdout]         assert!(check(&[(3, vec![20])]).is_ok());
[INFO] [stdout]         assert_eq!(junction_get(&junction, &2), Vec::<EntityId>::new());
[INFO] [stdout]         assert!(read_or_recover(&junction).check_index().is_ok());
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] stack backtrace:
[INFO] [stdout]    0:     0x5a49745cc881 - <<std[617be2ca0a6fd877]::sys::backtrace::BacktraceLock>::print::DisplayBacktrace as core[a373ad73a504971]::fmt::Display>::fmt
[INFO] [stdout]    1:     0x5a49745e469a - core[a373ad73a504971]::fmt::write
[INFO] [stdout]    2:     0x5a49745d10dc - <alloc[2e1b2a31b91974c5]::vec::Vec<u8> as core[a373ad73a504971]::io::write::Write>::write_fmt
[INFO] [stdout]    3:     0x5a49745a75d6 - std[617be2ca0a6fd877]::panicking::default_hook::{closure#0}
[INFO] [stdout]    4:     0x5a49745c4669 - std[617be2ca0a6fd877]::panicking::default_hook
[INFO] [stdout]    5:     0x5a497438fc20 - test[aada8e7e76ba119b]::test_main_inner::{closure#0}
[INFO] [stdout]    6:     0x5a49745c4822 - std[617be2ca0a6fd877]::panicking::panic_with_hook
[INFO] [stdout]    7:     0x5a49745a7682 - std[617be2ca0a6fd877]::panicking::panic_handler::{closure#0}
[INFO] [stdout]    8:     0x5a497459fc79 - std[617be2ca0a6fd877]::sys::backtrace::__rust_end_short_backtrace::<std[617be2ca0a6fd877]::panicking::panic_handler::{closure#0}, !>
[INFO] [stdout]    9:     0x5a49745a839d - __rustc[ea4f61d10b84890b]::rust_begin_unwind
[INFO] [stdout]   10:     0x5a49745e4ebc - core[a373ad73a504971]::panicking::panic_fmt
[INFO] [stdout]   11:     0x5a497430eb66 - rust_file_generation[bc330dc3ad16d42f]::use_cases::common::rust_code_generator::rust_code_generator_tests::assert_is_valid_rust
[INFO] [stdout]   12:     0x5a49743216e6 - <rust_file_generation[bc330dc3ad16d42f]::use_cases::common::rust_code_generator::rust_code_generator_tests::hashmap_store_indexes_exclusive_junctions_and_validates_without_walking_them::{closure#0} as core[a373ad73a504971]::ops::function::FnOnce<()>>::call_once
[INFO] [stdout]   13:     0x5a497438306b - test[aada8e7e76ba119b]::__rust_begin_short_backtrace::<core[a373ad73a504971]::result::Result<(), alloc[2e1b2a31b91974c5]::string::String>, fn() -> core[a373ad73a504971]::result::Result<(), alloc[2e1b2a31b91974c5]::string::String>>
[INFO] [stdout]   14:     0x5a4974390245 - test[aada8e7e76ba119b]::run_test::{closure#0}
[INFO] [stdout]   15:     0x5a4974389b34 - std[617be2ca0a6fd877]::sys::backtrace::__rust_begin_short_backtrace::<test[aada8e7e76ba119b]::run_test::{closure#1}, ()>
[INFO] [stdout]   16:     0x5a49743932d2 - <std[617be2ca0a6fd877]::thread::lifecycle::spawn_unchecked<test[aada8e7e76ba119b]::run_test::{closure#1}, ()>::{closure#1} as core[a373ad73a504971]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0}
[INFO] [stdout]   17:     0x5a49745cba6f - <std[617be2ca0a6fd877]::sys::thread::unix::Thread>::new::thread_start
[INFO] [stdout]   18:     0x78bb6f0cbdfa - <unknown>
[INFO] [stdout]   19:     0x78bb6f15f3d4 - clone
[INFO] [stdout]   20:                0x0 - <unknown>
[INFO] [stdout] 
[INFO] [stdout] ---- use_cases::common::rust_code_generator::rust_code_generator_tests::entity_units_of_work_template_wires_the_guard_and_renders_valid_rust stdout ----
[INFO] [stdout] 
[INFO] [stdout] thread 'use_cases::common::rust_code_generator::rust_code_generator_tests::entity_units_of_work_template_wires_the_guard_and_renders_valid_rust' (1225) panicked at src/use_cases/common/rust_code_generator/rust_code_generator_tests.rs:830:5:
[INFO] [stdout] entity_units_of_work: rendered template output is not valid Rust:
[INFO] [stdout] error: 'rustfmt' is not installed for the toolchain '1.100.0-beta.1-x86_64-unknown-linux-gnu'.
[INFO] [stdout] help: run `rustup component add --toolchain 1.100.0-beta.1-x86_64-unknown-linux-gnu rustfmt` to install it
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] --- rendered code ---
[INFO] [stdout] // Generated by Qleany v1.0.0 from entity_units_of_work.tera
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] use common::entities::Thing;
[INFO] [stdout] use anyhow::{Ok, Result};
[INFO] [stdout] use common::database::{db_context::DbContext, transactions::Transaction};
[INFO] [stdout] use common::database::{CommandUnitOfWork, QueryUnitOfWork};
[INFO] [stdout] use common::database::write_guard::WriteTransactionGuard;
[INFO] [stdout] use common::direct_access::repository_factory;
[INFO] [stdout] use common::direct_access::use_cases;
[INFO] [stdout] use common::event::{AllEvent, DirectAccessEntity, Event, EventBuffer, EventHub, Origin};
[INFO] [stdout] use common::snapshot::EntityTreeSnapshot;
[INFO] [stdout] use common::types;
[INFO] [stdout] use common::types::EntityId;
[INFO] [stdout] use std::cell::RefCell;
[INFO] [stdout] use std::sync::Arc;
[INFO] [stdout] use common::direct_access::thing::ThingRelationshipField;
[INFO] [stdout] 
[INFO] [stdout] /// The error every UoW method returns when it is called before
[INFO] [stdout] /// `begin_transaction`.
[INFO] [stdout] ///
[INFO] [stdout] /// This used to be `.expect("Transaction not started")`. Calling a UoW method
[INFO] [stdout] /// out of order is a caller bug, but panicking on it is the wrong report: a
[INFO] [stdout] /// unit of work runs inside a use case, and several of those run as long
[INFO] [stdout] /// operations on a background thread, where `common::long_operation`'s
[INFO] [stdout] /// `catch_unwind` turns a failure into a reported `Failed` — but only if the
[INFO] [stdout] /// failure is an `Err` rather than a process-killing panic. Every method here
[INFO] [stdout] /// already returns `Result`, so `?` costs nothing and keeps the bug diagnosable
[INFO] [stdout] /// instead of fatal.
[INFO] [stdout] fn no_transaction() -> anyhow::Error {
[INFO] [stdout]     anyhow::anyhow!(
[INFO] [stdout]         "unit of work used before `begin_transaction` — no transaction is open on this UoW"
[INFO] [stdout]     )
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] // ===========================================================================
[INFO] [stdout] // Write UoW
[INFO] [stdout] // ===========================================================================
[INFO] [stdout] 
[INFO] [stdout] pub struct ThingWriteUoW {
[INFO] [stdout]     context: DbContext,
[INFO] [stdout]     transaction: Option<Transaction>,
[INFO] [stdout]     // RAII: acquired in `begin_transaction`, released via `commit`/`rollback`
[INFO] [stdout]     // clearing it — see `common::database::write_guard`'s module doc for the
[INFO] [stdout]     // single-write-transaction-per-store invariant this enforces.
[INFO] [stdout]     write_guard: Option<WriteTransactionGuard>,
[INFO] [stdout]     event_hub: Arc<EventHub>,
[INFO] [stdout]     event_buffer: RefCell<EventBuffer>,
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl ThingWriteUoW {
[INFO] [stdout]     pub fn new(db_context: &DbContext, event_hub: &Arc<EventHub>) -> Self {
[INFO] [stdout]         ThingWriteUoW {
[INFO] [stdout]             context: db_context.clone(),
[INFO] [stdout]             transaction: None,
[INFO] [stdout]             write_guard: None,
[INFO] [stdout]             event_hub: event_hub.clone(),
[INFO] [stdout]             event_buffer: RefCell::new(EventBuffer::new()),
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl CommandUnitOfWork for ThingWriteUoW {
[INFO] [stdout]     fn begin_transaction(&mut self) -> Result<()> {
[INFO] [stdout]         // Acquire BEFORE the write transaction itself — see
[INFO] [stdout]         // `common::database::write_guard`'s module doc.
[INFO] [stdout]         self.write_guard = Some(WriteTransactionGuard::acquire(&self.context, "thing_direct_write")?);
[INFO] [stdout]         self.transaction = Some(Transaction::begin_write_transaction(&self.context)?);
[INFO] [stdout]         self.event_buffer.get_mut().begin_buffering();
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn commit(&mut self) -> Result<()> {
[INFO] [stdout]         self.transaction.take()
[INFO] [stdout]             .ok_or_else(|| anyhow::anyhow!("No active transaction"))?
[INFO] [stdout]             .commit()?;
[INFO] [stdout]         self.write_guard = None;
[INFO] [stdout]         for event in self.event_buffer.get_mut().flush() {
[INFO] [stdout]             self.event_hub.send_event(event);
[INFO] [stdout]         }
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn rollback(&mut self) -> Result<()> {
[INFO] [stdout]         self.transaction.take()
[INFO] [stdout]             .ok_or_else(|| anyhow::anyhow!("No active transaction"))?
[INFO] [stdout]             .rollback()?;
[INFO] [stdout]         self.write_guard = None;
[INFO] [stdout]         self.event_buffer.get_mut().discard();
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn create_savepoint(&self) -> Result<types::Savepoint> {
[INFO] [stdout]         self.transaction.as_ref()
[INFO] [stdout]             .ok_or_else(|| anyhow::anyhow!("No active transaction"))?
[INFO] [stdout]             .create_savepoint()
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn restore_to_savepoint(&mut self, savepoint: types::Savepoint) -> Result<()> {
[INFO] [stdout]         let mut transaction = self.transaction.take()
[INFO] [stdout]             .ok_or_else(|| anyhow::anyhow!("No active transaction"))?;
[INFO] [stdout]         transaction.restore_to_savepoint(savepoint)?;
[INFO] [stdout] 
[INFO] [stdout]         // Discard buffered events — savepoint restore invalidated them
[INFO] [stdout]         self.event_buffer.get_mut().discard();
[INFO] [stdout] 
[INFO] [stdout]         // Send Reset immediately (not buffered — UI must refresh now)
[INFO] [stdout]         self.event_hub.send_event(Event {
[INFO] [stdout]             origin: Origin::DirectAccess(DirectAccessEntity::All(AllEvent::Reset)),
[INFO] [stdout]             ids: vec![],
[INFO] [stdout]             data: None,
[INFO] [stdout]         });
[INFO] [stdout] 
[INFO] [stdout]         // Recreate the transaction after restoring to savepoint
[INFO] [stdout]         self.transaction = Some(transaction);
[INFO] [stdout] 
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl use_cases::WriteUoW for ThingWriteUoW {
[INFO] [stdout]     type Entity = Thing;
[INFO] [stdout] 
[INFO] [stdout]     fn get(&self, id: &EntityId) -> Result<Option<Thing>> {
[INFO] [stdout]         let transaction = self.transaction.as_ref().ok_or_else(no_transaction)?;
[INFO] [stdout]         let repo = repository_factory::write::create_thing_repository(transaction)?;
[INFO] [stdout]         Ok(repo.get(id)?)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn get_multi(&self, ids: &[EntityId]) -> Result<Vec<Option<Thing>>> {
[INFO] [stdout]         let transaction = self.transaction.as_ref().ok_or_else(no_transaction)?;
[INFO] [stderr] error: test failed, to rerun pass `--lib`
[INFO] [stdout]         let repo = repository_factory::write::create_thing_repository(transaction)?;
[INFO] [stdout]         Ok(repo.get_multi(ids)?)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn get_all(&self) -> Result<Vec<Thing>> {
[INFO] [stdout]         let transaction = self.transaction.as_ref().ok_or_else(no_transaction)?;
[INFO] [stdout]         let repo = repository_factory::write::create_thing_repository(transaction)?;
[INFO] [stdout]         Ok(repo.get_all()?)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn create_orphan_multi(&self, entities: &[Thing]) -> Result<Vec<Thing>> {
[INFO] [stdout]         let transaction = self.transaction.as_ref().ok_or_else(no_transaction)?;
[INFO] [stdout]         let mut repo = repository_factory::write::create_thing_repository(transaction)?;
[INFO] [stdout]         let mut event_buffer = self.event_buffer.borrow_mut();
[INFO] [stdout]         Ok(repo.create_orphan_multi(&mut event_buffer, entities)?)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn update_multi(&self, entities: &[Thing]) -> Result<Vec<Thing>> {
[INFO] [stdout]         let transaction = self.transaction.as_ref().ok_or_else(no_transaction)?;
[INFO] [stdout]         let mut repo = repository_factory::write::create_thing_repository(transaction)?;
[INFO] [stdout]         let mut event_buffer = self.event_buffer.borrow_mut();
[INFO] [stdout]         Ok(repo.update_multi(&mut event_buffer, entities)?)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn update_with_relationships_multi(&self, entities: &[Thing]) -> Result<Vec<Thing>> {
[INFO] [stdout]         let transaction = self.transaction.as_ref().ok_or_else(no_transaction)?;
[INFO] [stdout]         let mut repo = repository_factory::write::create_thing_repository(transaction)?;
[INFO] [stdout]         let mut event_buffer = self.event_buffer.borrow_mut();
[INFO] [stdout]         Ok(repo.update_with_relationships_multi(&mut event_buffer, entities)?)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn remove(&self, id: &EntityId) -> Result<()> {
[INFO] [stdout]         let transaction = self.transaction.as_ref().ok_or_else(no_transaction)?;
[INFO] [stdout]         let mut repo = repository_factory::write::create_thing_repository(transaction)?;
[INFO] [stdout]         let mut event_buffer = self.event_buffer.borrow_mut();
[INFO] [stdout]         Ok(repo.remove(&mut event_buffer, id)?)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn remove_multi(&self, ids: &[EntityId]) -> Result<()> {
[INFO] [stdout]         let transaction = self.transaction.as_ref().ok_or_else(no_transaction)?;
[INFO] [stdout]         let mut repo = repository_factory::write::create_thing_repository(transaction)?;
[INFO] [stdout]         let mut event_buffer = self.event_buffer.borrow_mut();
[INFO] [stdout]         Ok(repo.remove_multi(&mut event_buffer, ids)?)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn snapshot(&self, ids: &[EntityId]) -> Result<EntityTreeSnapshot> {
[INFO] [stdout]         let transaction = self.transaction.as_ref().ok_or_else(no_transaction)?;
[INFO] [stdout]         let repo = repository_factory::write::create_thing_repository(transaction)?;
[INFO] [stdout]         Ok(repo.snapshot(ids)?)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn restore(&self, snap: &EntityTreeSnapshot) -> Result<()> {
[INFO] [stdout]         let transaction = self.transaction.as_ref().ok_or_else(no_transaction)?;
[INFO] [stdout]         let mut repo = repository_factory::write::create_thing_repository(transaction)?;
[INFO] [stdout]         let mut event_buffer = self.event_buffer.borrow_mut();
[INFO] [stdout]         Ok(repo.restore(&mut event_buffer, snap)?)
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl use_cases::OwnedWriteUoW for ThingWriteUoW {
[INFO] [stdout]     fn create_multi(
[INFO] [stdout]         &self,
[INFO] [stdout]         entities: &[Thing],
[INFO] [stdout]         owner_id: EntityId,
[INFO] [stdout]         index: i32,
[INFO] [stdout]     ) -> Result<Vec<Thing>> {
[INFO] [stdout]         let transaction = self.transaction.as_ref().ok_or_else(no_transaction)?;
[INFO] [stdout]         let mut repo = repository_factory::write::create_thing_repository(transaction)?;
[INFO] [stdout]         let mut event_buffer = self.event_buffer.borrow_mut();
[INFO] [stdout]         Ok(repo.create_multi(&mut event_buffer, entities, owner_id, index)?)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn get_relationships_from_owner(&self, owner_id: &EntityId) -> Result<Vec<EntityId>> {
[INFO] [stdout]         let transaction = self.transaction.as_ref().ok_or_else(no_transaction)?;
[INFO] [stdout]         let repo = repository_factory::write::create_thing_repository(transaction)?;
[INFO] [stdout]         Ok(repo.get_relationships_from_owner(owner_id)?)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn set_relationships_in_owner(&self, owner_id: &EntityId, ids: &[EntityId]) -> Result<()> {
[INFO] [stdout]         let transaction = self.transaction.as_ref().ok_or_else(no_transaction)?;
[INFO] [stdout]         let mut repo = repository_factory::write::create_thing_repository(transaction)?;
[INFO] [stdout]         let mut event_buffer = self.event_buffer.borrow_mut();
[INFO] [stdout]         repo.set_relationships_in_owner(&mut event_buffer, owner_id, ids)?;
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl use_cases::WriteRelUoW<ThingRelationshipField> for ThingWriteUoW {
[INFO] [stdout]     fn get_relationship(
[INFO] [stdout]         &self,
[INFO] [stdout]         id: &EntityId,
[INFO] [stdout]         field: &ThingRelationshipField,
[INFO] [stdout]     ) -> Result<Vec<EntityId>> {
[INFO] [stdout]         let transaction = self.transaction.as_ref().ok_or_else(no_transaction)?;
[INFO] [stdout]         let repo = repository_factory::write::create_thing_repository(transaction)?;
[INFO] [stdout]         Ok(repo.get_relationship(id, field)?)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn set_relationship(
[INFO] [stdout]         &self,
[INFO] [stdout]         id: &EntityId,
[INFO] [stdout]         field: &ThingRelationshipField,
[INFO] [stdout]         right_ids: &[EntityId],
[INFO] [stdout]     ) -> Result<()> {
[INFO] [stdout]         let transaction = self.transaction.as_ref().ok_or_else(no_transaction)?;
[INFO] [stdout]         let mut repo = repository_factory::write::create_thing_repository(transaction)?;
[INFO] [stdout]         let mut event_buffer = self.event_buffer.borrow_mut();
[INFO] [stdout]         repo.set_relationship(&mut event_buffer, id, field, right_ids)?;
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn move_relationship(
[INFO] [stdout]         &self,
[INFO] [stdout]         id: &EntityId,
[INFO] [stdout]         field: &ThingRelationshipField,
[INFO] [stdout]         ids_to_move: &[EntityId],
[INFO] [stdout]         new_index: i32,
[INFO] [stdout]     ) -> Result<Vec<EntityId>> {
[INFO] [stdout]         let transaction = self.transaction.as_ref().ok_or_else(no_transaction)?;
[INFO] [stdout]         let mut repo = repository_factory::write::create_thing_repository(transaction)?;
[INFO] [stdout]         let mut event_buffer = self.event_buffer.borrow_mut();
[INFO] [stdout]         Ok(repo.move_relationship_ids(&mut event_buffer, id, field, ids_to_move, new_index)?)
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] // ---------------------------------------------------------------------------
[INFO] [stdout] // Write UoW Factory
[INFO] [stdout] // ---------------------------------------------------------------------------
[INFO] [stdout] 
[INFO] [stdout] pub struct ThingWriteUoWFactory {
[INFO] [stdout]     context: DbContext,
[INFO] [stdout]     event_hub: Arc<EventHub>,
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl ThingWriteUoWFactory {
[INFO] [stdout]     pub fn new(db_context: &DbContext, event_hub: &Arc<EventHub>) -> Self {
[INFO] [stdout]         ThingWriteUoWFactory {
[INFO] [stdout]             context: db_context.clone(),
[INFO] [stdout]             event_hub: event_hub.clone(),
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl use_cases::WriteUoWFactory for ThingWriteUoWFactory {
[INFO] [stdout]     type Entity = Thing;
[INFO] [stdout]     fn create(&self) -> Box<dyn use_cases::WriteUoW<Entity = Thing>> {
[INFO] [stdout]         Box::new(ThingWriteUoW::new(&self.context, &self.event_hub))
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl use_cases::OwnedWriteUoWFactory for ThingWriteUoWFactory {
[INFO] [stdout]     type Entity = Thing;
[INFO] [stdout]     fn create(&self) -> Box<dyn use_cases::OwnedWriteUoW<Entity = Thing>> {
[INFO] [stdout]         Box::new(ThingWriteUoW::new(&self.context, &self.event_hub))
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl use_cases::WriteRelUoWFactory<ThingRelationshipField> for ThingWriteUoWFactory {
[INFO] [stdout]     fn create(&self) -> Box<dyn use_cases::WriteRelUoW<ThingRelationshipField>> {
[INFO] [stdout]         Box::new(ThingWriteUoW::new(&self.context, &self.event_hub))
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] // ===========================================================================
[INFO] [stdout] // Read-Only UoW
[INFO] [stdout] // ===========================================================================
[INFO] [stdout] 
[INFO] [stdout] pub struct ThingReadUoW {
[INFO] [stdout]     context: DbContext,
[INFO] [stdout]     transaction: RefCell<Option<Transaction>>,
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl ThingReadUoW {
[INFO] [stdout]     pub fn new(db_context: &DbContext) -> Self {
[INFO] [stdout]         ThingReadUoW {
[INFO] [stdout]             context: db_context.clone(),
[INFO] [stdout]             transaction: RefCell::new(None),
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl QueryUnitOfWork for ThingReadUoW {
[INFO] [stdout]     fn begin_transaction(&self) -> Result<()> {
[INFO] [stdout]         self.transaction
[INFO] [stdout]             .replace(Some(Transaction::begin_read_transaction(&self.context)?));
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn end_transaction(&self) -> Result<()> {
[INFO] [stdout]         self.transaction.take()
[INFO] [stdout]             .ok_or_else(|| anyhow::anyhow!("No active transaction"))?
[INFO] [stdout]             .end_read_transaction()?;
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl use_cases::ReadUoW for ThingReadUoW {
[INFO] [stdout]     type Entity = Thing;
[INFO] [stdout] 
[INFO] [stdout]     fn get(&self, id: &EntityId) -> Result<Option<Thing>> {
[INFO] [stdout]         let transaction = self.transaction.borrow();
[INFO] [stdout]         let repo = repository_factory::read::create_thing_repository(transaction.as_ref().ok_or_else(|| anyhow::anyhow!("No active transaction"))?)?;
[INFO] [stdout]         Ok(repo.get(id)?)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn get_multi(&self, ids: &[EntityId]) -> Result<Vec<Option<Thing>>> {
[INFO] [stdout]         let transaction = self.transaction.borrow();
[INFO] [stdout]         let repo = repository_factory::read::create_thing_repository(transaction.as_ref().ok_or_else(|| anyhow::anyhow!("No active transaction"))?)?;
[INFO] [stdout]         Ok(repo.get_multi(ids)?)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn get_all(&self) -> Result<Vec<Thing>> {
[INFO] [stdout]         let transaction = self.transaction.borrow();
[INFO] [stdout]         let repo = repository_factory::read::create_thing_repository(transaction.as_ref().ok_or_else(|| anyhow::anyhow!("No active transaction"))?)?;
[INFO] [stdout]         Ok(repo.get_all()?)
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl use_cases::ReadRelUoW<ThingRelationshipField> for ThingReadUoW {
[INFO] [stdout]     fn get_relationship(&self, id: &EntityId, field: &ThingRelationshipField) -> Result<Vec<EntityId>> {
[INFO] [stdout]         let transaction = self.transaction.borrow();
[INFO] [stdout]         let repo = repository_factory::read::create_thing_repository(transaction.as_ref().ok_or_else(|| anyhow::anyhow!("No active transaction"))?)?;
[INFO] [stdout]         Ok(repo.get_relationship(id, field)?)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn get_relationship_many(&self, ids: &[EntityId], field: &ThingRelationshipField) -> Result<std::collections::HashMap<EntityId, Vec<EntityId>>> {
[INFO] [stdout]         let transaction = self.transaction.borrow();
[INFO] [stdout]         let repo = repository_factory::read::create_thing_repository(transaction.as_ref().ok_or_else(|| anyhow::anyhow!("No active transaction"))?)?;
[INFO] [stdout]         Ok(repo.get_relationship_many(ids, field)?)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn get_relationship_count(&self, id: &EntityId, field: &ThingRelationshipField) -> Result<usize> {
[INFO] [stdout]         let transaction = self.transaction.borrow();
[INFO] [stdout]         let repo = repository_factory::read::create_thing_repository(transaction.as_ref().ok_or_else(|| anyhow::anyhow!("No active transaction"))?)?;
[INFO] [stdout]         Ok(repo.get_relationship_count(id, field)?)
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn get_relationship_in_range(&self, id: &EntityId, field: &ThingRelationshipField, offset: usize, limit: usize) -> Result<Vec<EntityId>> {
[INFO] [stdout]         let transaction = self.transaction.borrow();
[INFO] [stdout]         let repo = repository_factory::read::create_thing_repository(transaction.as_ref().ok_or_else(|| anyhow::anyhow!("No active transaction"))?)?;
[INFO] [stdout]         Ok(repo.get_relationship_in_range(id, field, offset, limit)?)
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] // ---------------------------------------------------------------------------
[INFO] [stdout] // Read-Only UoW Factory
[INFO] [stdout] // ---------------------------------------------------------------------------
[INFO] [stdout] 
[INFO] [stdout] pub struct ThingReadUoWFactory {
[INFO] [stdout]     context: DbContext,
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl ThingReadUoWFactory {
[INFO] [stdout]     pub fn new(db_context: &DbContext) -> Self {
[INFO] [stdout]         ThingReadUoWFactory {
[INFO] [stdout]             context: db_context.clone(),
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl use_cases::ReadUoWFactory for ThingReadUoWFactory {
[INFO] [stdout]     type Entity = Thing;
[INFO] [stdout]     fn create(&self) -> Box<dyn use_cases::ReadUoW<Entity = Thing>> {
[INFO] [stdout]         Box::new(ThingReadUoW::new(&self.context))
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl use_cases::ReadRelUoWFactory<ThingRelationshipField> for ThingReadUoWFactory {
[INFO] [stdout]     fn create(&self) -> Box<dyn use_cases::ReadRelUoW<ThingRelationshipField>> {
[INFO] [stdout]         Box::new(ThingReadUoW::new(&self.context))
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] stack backtrace:
[INFO] [stdout]    0:     0x5a49745cc881 - <<std[617be2ca0a6fd877]::sys::backtrace::BacktraceLock>::print::DisplayBacktrace as core[a373ad73a504971]::fmt::Display>::fmt
[INFO] [stdout]    1:     0x5a49745e469a - core[a373ad73a504971]::fmt::write
[INFO] [stdout]    2:     0x5a49745d10dc - <alloc[2e1b2a31b91974c5]::vec::Vec<u8> as core[a373ad73a504971]::io::write::Write>::write_fmt
[INFO] [stdout]    3:     0x5a49745a75d6 - std[617be2ca0a6fd877]::panicking::default_hook::{closure#0}
[INFO] [stdout]    4:     0x5a49745c4669 - std[617be2ca0a6fd877]::panicking::default_hook
[INFO] [stdout]    5:     0x5a497438fc20 - test[aada8e7e76ba119b]::test_main_inner::{closure#0}
[INFO] [stdout]    6:     0x5a49745c4822 - std[617be2ca0a6fd877]::panicking::panic_with_hook
[INFO] [stdout]    7:     0x5a49745a7682 - std[617be2ca0a6fd877]::panicking::panic_handler::{closure#0}
[INFO] [stdout]    8:     0x5a497459fc79 - std[617be2ca0a6fd877]::sys::backtrace::__rust_end_short_backtrace::<std[617be2ca0a6fd877]::panicking::panic_handler::{closure#0}, !>
[INFO] [stdout]    9:     0x5a49745a839d - __rustc[ea4f61d10b84890b]::rust_begin_unwind
[INFO] [stdout]   10:     0x5a49745e4ebc - core[a373ad73a504971]::panicking::panic_fmt
[INFO] [stdout]   11:     0x5a497430eb66 - rust_file_generation[bc330dc3ad16d42f]::use_cases::common::rust_code_generator::rust_code_generator_tests::assert_is_valid_rust
[INFO] [stdout]   12:     0x5a49743208dd - <rust_file_generation[bc330dc3ad16d42f]::use_cases::common::rust_code_generator::rust_code_generator_tests::entity_units_of_work_template_wires_the_guard_and_renders_valid_rust::{closure#0} as core[a373ad73a504971]::ops::function::FnOnce<()>>::call_once
[INFO] [stdout]   13:     0x5a497438306b - test[aada8e7e76ba119b]::__rust_begin_short_backtrace::<core[a373ad73a504971]::result::Result<(), alloc[2e1b2a31b91974c5]::string::String>, fn() -> core[a373ad73a504971]::result::Result<(), alloc[2e1b2a31b91974c5]::string::String>>
[INFO] [stdout]   14:     0x5a4974390245 - test[aada8e7e76ba119b]::run_test::{closure#0}
[INFO] [stdout]   15:     0x5a4974389b34 - std[617be2ca0a6fd877]::sys::backtrace::__rust_begin_short_backtrace::<test[aada8e7e76ba119b]::run_test::{closure#1}, ()>
[INFO] [stdout]   16:     0x5a49743932d2 - <std[617be2ca0a6fd877]::thread::lifecycle::spawn_unchecked<test[aada8e7e76ba119b]::run_test::{closure#1}, ()>::{closure#1} as core[a373ad73a504971]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0}
[INFO] [stdout]   17:     0x5a49745cba6f - <std[617be2ca0a6fd877]::sys::thread::unix::Thread>::new::thread_start
[INFO] [stdout]   18:     0x78bb6f0cbdfa - <unknown>
[INFO] [stdout]   19:     0x78bb6f15f3d4 - clone
[INFO] [stdout]   20:                0x0 - <unknown>
[INFO] [stdout] 
[INFO] [stdout] ---- use_cases::common::rust_code_generator::rust_code_generator_tests::feature_use_case_uow_read_only_template_has_no_guard_and_renders_valid_rust stdout ----
[INFO] [stdout] 
[INFO] [stdout] thread 'use_cases::common::rust_code_generator::rust_code_generator_tests::feature_use_case_uow_read_only_template_has_no_guard_and_renders_valid_rust' (1228) panicked at src/use_cases/common/rust_code_generator/rust_code_generator_tests.rs:830:5:
[INFO] [stdout] feature_use_case_uow (read-only, non-long-operation): rendered template output is not valid Rust:
[INFO] [stdout] error: 'rustfmt' is not installed for the toolchain '1.100.0-beta.1-x86_64-unknown-linux-gnu'.
[INFO] [stdout] help: run `rustup component add --toolchain 1.100.0-beta.1-x86_64-unknown-linux-gnu rustfmt` to install it
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] --- rendered code ---
[INFO] [stdout] // Generated by Qleany v1.0.0 from feature_use_case_uow.tera
[INFO] [stdout] 
[INFO] [stdout] use crate::use_cases::do_thing_uc::{
[INFO] [stdout]     DoThingUnitOfWorkFactoryTrait, DoThingUnitOfWorkTrait,
[INFO] [stdout] };
[INFO] [stdout] use anyhow::{Ok, Result};
[INFO] [stdout] use common::database::{db_context::DbContext, transactions::Transaction};
[INFO] [stdout] #[allow(unused_imports)]
[INFO] [stdout] use common::entities:: {
[INFO] [stdout] Thing,
[INFO] [stdout] };
[INFO] [stdout] use common::database::QueryUnitOfWork;
[INFO] [stdout] use common::event::{Event, EventHub, Origin};
[INFO] [stdout] use std::cell::RefCell;
[INFO] [stdout] use std::sync::Arc;
[INFO] [stdout] use common::event::DoingEvent::DoThing;
[INFO] [stdout] #[allow(unused_imports)]
[INFO] [stdout] use common::types;
[INFO] [stdout] #[allow(unused_imports)]
[INFO] [stdout] use common::types::EntityId;
[INFO] [stdout] 
[INFO] [stdout] // Unit of work for DoThing
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] pub struct DoThingUnitOfWork {
[INFO] [stdout]     context: DbContext,
[INFO] [stdout]     transaction: RefCell<Option<Transaction>>,
[INFO] [stdout]     event_hub: Arc<EventHub>,
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl DoThingUnitOfWork {
[INFO] [stdout]     pub fn new(db_context: &DbContext, event_hub: &Arc<EventHub>) -> Self {
[INFO] [stdout]         DoThingUnitOfWork {
[INFO] [stdout]             context: db_context.clone(),
[INFO] [stdout]             transaction: RefCell::new(None),
[INFO] [stdout]             event_hub: event_hub.clone(),
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl QueryUnitOfWork for DoThingUnitOfWork {
[INFO] [stdout]     fn begin_transaction(&self) -> Result<()> {
[INFO] [stdout]         self.transaction
[INFO] [stdout]             .replace(Some(Transaction::begin_read_transaction(&self.context)?));
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn end_transaction(&self) -> Result<()> {
[INFO] [stdout]         self.transaction.take()
[INFO] [stdout]             .ok_or_else(|| anyhow::anyhow!("No active transaction"))?
[INFO] [stdout]             .end_read_transaction()?;
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] //TODO: adapt entities and actions to real use :
[INFO] [stdout] // GetRO, GetMultiRO, GetRelationshipRO, GetRelationshipsFromRightIdsRO
[INFO] [stdout] //
[INFO] [stdout] // You have here a read-only unit of work.
[INFO] [stdout] //
[INFO] [stdout] // RO means Read Only, so *RO actions should be used here.
[INFO] [stdout] // Do not mix read-only and write actions in the same unit of work.
[INFO] [stdout] //
[INFO] [stdout] // Exactly the same macros must be set in the use case uow trait file in ../use_cases/do_thing_uc.rs
[INFO] [stdout] //
[INFO] [stdout] #[macros::uow_action(entity = "Thing", action = "GetRO")]
[INFO] [stdout] #[macros::uow_action(entity = "Thing", action = "GetMultiRO")]
[INFO] [stdout] impl DoThingUnitOfWorkTrait for DoThingUnitOfWork {
[INFO] [stdout]     fn publish_do_thing_event(&self, ids: Vec<EntityId>, data: Option<String>) {
[INFO] [stdout]         self.event_hub.send_event(Event {
[INFO] [stdout]             origin: Origin::Doing(DoThing),
[INFO] [stdout]             ids,
[INFO] [stdout]             data,
[INFO] [stdout]         });
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] pub struct DoThingUnitOfWorkFactory {
[INFO] [stdout]     context: DbContext,
[INFO] [stdout]     event_hub: Arc<EventHub>,
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl DoThingUnitOfWorkFactory {
[INFO] [stdout]     pub fn new(db_context: &DbContext, event_hub: &Arc<EventHub>) -> Self {
[INFO] [stdout]         DoThingUnitOfWorkFactory {
[INFO] [stdout]             context: db_context.clone(),
[INFO] [stdout]             event_hub: event_hub.clone(),
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl DoThingUnitOfWorkFactoryTrait for DoThingUnitOfWorkFactory {
[INFO] [stdout]     fn create(&self) -> Box<dyn DoThingUnitOfWorkTrait> {
[INFO] [stdout]         Box::new(DoThingUnitOfWork::new(&self.context, &self.event_hub))
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] stack backtrace:
[INFO] [stdout]    0:     0x5a49745cc881 - <<std[617be2ca0a6fd877]::sys::backtrace::BacktraceLock>::print::DisplayBacktrace as core[a373ad73a504971]::fmt::Display>::fmt
[INFO] [stdout]    1:     0x5a49745e469a - core[a373ad73a504971]::fmt::write
[INFO] [stdout]    2:     0x5a49745d10dc - <alloc[2e1b2a31b91974c5]::vec::Vec<u8> as core[a373ad73a504971]::io::write::Write>::write_fmt
[INFO] [stdout]    3:     0x5a49745a75d6 - std[617be2ca0a6fd877]::panicking::default_hook::{closure#0}
[INFO] [stdout]    4:     0x5a49745c4669 - std[617be2ca0a6fd877]::panicking::default_hook
[INFO] [stdout]    5:     0x5a497438fc20 - test[aada8e7e76ba119b]::test_main_inner::{closure#0}
[INFO] [stdout]    6:     0x5a49745c4822 - std[617be2ca0a6fd877]::panicking::panic_with_hook
[INFO] [stdout]    7:     0x5a49745a7682 - std[617be2ca0a6fd877]::panicking::panic_handler::{closure#0}
[INFO] [stdout]    8:     0x5a497459fc79 - std[617be2ca0a6fd877]::sys::backtrace::__rust_end_short_backtrace::<std[617be2ca0a6fd877]::panicking::panic_handler::{closure#0}, !>
[INFO] [stdout]    9:     0x5a49745a839d - __rustc[ea4f61d10b84890b]::rust_begin_unwind
[INFO] [stdout]   10:     0x5a49745e4ebc - core[a373ad73a504971]::panicking::panic_fmt
[INFO] [stdout]   11:     0x5a497430eb66 - rust_file_generation[bc330dc3ad16d42f]::use_cases::common::rust_code_generator::rust_code_generator_tests::assert_is_valid_rust
[INFO] [stdout]   12:     0x5a4974320fa9 - <rust_file_generation[bc330dc3ad16d42f]::use_cases::common::rust_code_generator::rust_code_generator_tests::feature_use_case_uow_read_only_template_has_no_guard_and_renders_valid_rust::{closure#0} as core[a373ad73a504971]::ops::function::FnOnce<()>>::call_once
[INFO] [stdout]   13:     0x5a497438306b - test[aada8e7e76ba119b]::__rust_begin_short_backtrace::<core[a373ad73a504971]::result::Result<(), alloc[2e1b2a31b91974c5]::string::String>, fn() -> core[a373ad73a504971]::result::Result<(), alloc[2e1b2a31b91974c5]::string::String>>
[INFO] [stdout]   14:     0x5a4974390245 - test[aada8e7e76ba119b]::run_test::{closure#0}
[INFO] [stdout]   15:     0x5a4974389b34 - std[617be2ca0a6fd877]::sys::backtrace::__rust_begin_short_backtrace::<test[aada8e7e76ba119b]::run_test::{closure#1}, ()>
[INFO] [stdout]   16:     0x5a49743932d2 - <std[617be2ca0a6fd877]::thread::lifecycle::spawn_unchecked<test[aada8e7e76ba119b]::run_test::{closure#1}, ()>::{closure#1} as core[a373ad73a504971]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0}
[INFO] [stdout]   17:     0x5a49745cba6f - <std[617be2ca0a6fd877]::sys::thread::unix::Thread>::new::thread_start
[INFO] [stdout]   18:     0x78bb6f0cbdfa - <unknown>
[INFO] [stdout]   19:     0x78bb6f15f3d4 - clone
[INFO] [stdout]   20:                0x0 - <unknown>
[INFO] [stdout] 
[INFO] [stdout] ---- use_cases::common::rust_code_generator::rust_code_generator_tests::write_guard_template_renders_valid_rust stdout ----
[INFO] [stdout] 
[INFO] [stdout] thread 'use_cases::common::rust_code_generator::rust_code_generator_tests::write_guard_template_renders_valid_rust' (1236) panicked at src/use_cases/common/rust_code_generator/rust_code_generator_tests.rs:830:5:
[INFO] [stdout] write_guard: rendered template output is not valid Rust:
[INFO] [stdout] error: 'rustfmt' is not installed for the toolchain '1.100.0-beta.1-x86_64-unknown-linux-gnu'.
[INFO] [stdout] help: run `rustup component add --toolchain 1.100.0-beta.1-x86_64-unknown-linux-gnu rustfmt` to install it
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] --- rendered code ---
[INFO] [stdout] // Generated by Qleany v1.0.0 from write_guard.tera
[INFO] [stdout] 
[INFO] [stdout] //! Enforces the single-write-transaction-per-store invariant.
[INFO] [stdout] //!
[INFO] [stdout] //! ## Why this exists
[INFO] [stdout] //!
[INFO] [stdout] //! [`Transaction::begin_write_transaction`](super::transactions::Transaction::begin_write_transaction)
[INFO] [stdout] //! takes an unconditional WHOLE-STORE savepoint via
[INFO] [stdout] //! `HashMapStore::create_savepoint`, whose own doc comment states the
[INFO] [stdout] //! assumption directly: taken "on the single writer thread with no
[INFO] [stdout] //! concurrent writer". `rollback()` and the `Drop` safety net on
[INFO] [stdout] //! `Transaction` both restore that savepoint wholesale — not just the rows
[INFO] [stdout] //! the transaction itself touched. That is intended behaviour for a single
[INFO] [stdout] //! writer; it silently breaks the moment two write transactions on the same
[INFO] [stdout] //! store are ever open at once. If a second write transaction were opened
[INFO] [stdout] //! concurrently with one already in flight, the second transaction's own
[INFO] [stdout] //! `commit`/`rollback`/`Drop` would silently roll back to a savepoint taken
[INFO] [stdout] //! *before* the first transaction's edits ever existed, erasing them with no
[INFO] [stdout] //! error and no event.
[INFO] [stdout] //!
[INFO] [stdout] //! [`WriteTransactionGuard`] turns that assumption into an assertion. Every
[INFO] [stdout] //! generated `CommandUnitOfWork::begin_transaction` acquires one for the
[INFO] [stdout] //! entire span its write transaction may be open (see `commit`/`rollback` on
[INFO] [stdout] //! any generated `*WriteUoW`/`*UnitOfWork`, which clear it). A second
[INFO] [stdout] //! concurrent acquisition **on the same store** panics in debug builds —
[INFO] [stdout] //! loud, immediate, naming the call site — and returns an error in release
[INFO] [stdout] //! builds, so a shipped build degrades to a reported failure instead of
[INFO] [stdout] //! silently corrupting the store.
[INFO] [stdout] //!
[INFO] [stdout] //! Because the acquire/release is generated into every write-transaction
[INFO] [stdout] //! call site's `begin_transaction`/`commit`/`rollback` (both the per-entity
[INFO] [stdout] //! `direct_access` `WriteUoW`s and the per-use-case feature `UnitOfWork`s),
[INFO] [stdout] //! this coverage never drifts out of sync with the rest of the generated
[INFO] [stdout] //! backend the way a hand-maintained list of call sites would: a new entity
[INFO] [stdout] //! or use case picks up the guard the moment it is generated, with nothing
[INFO] [stdout] //! left for the application developer to remember.
[INFO] [stdout] //!
[INFO] [stdout] //! ## Why scoped per store, not one bare global flag
[INFO] [stdout] //!
[INFO] [stdout] //! A production app constructs exactly one `DbContext` (hence one store) for
[INFO] [stdout] //! the whole process, so a single process-wide flag would be an equally
[INFO] [stdout] //! correct proxy for "a write transaction is open on THE store" there. But
[INFO] [stdout] //! tests often build a fresh, independent `DbContext`/store per `#[test]`
[INFO] [stdout] //! fn, and `cargo test`'s default runner executes those fns concurrently on
[INFO] [stdout] //! separate threads — two fns opening a write transaction on their OWN
[INFO] [stdout] //! unrelated stores can legitimately overlap in wall-clock time. A bare
[INFO] [stdout] //! process-wide flag would make those spuriously trip each other's guard.
[INFO] [stdout] //! Keying by the store's `Arc` pointer identity models the real invariant
[INFO] [stdout] //! (one writer *per store*, not one writer *ever*) without that flakiness —
[INFO] [stdout] //! and needs no test-only carve-out to stay correct.
[INFO] [stdout] //!
[INFO] [stdout] //! ## Holding the `Arc` — no stale-pointer ABA
[INFO] [stdout] //!
[INFO] [stdout] //! The map is keyed by `Arc::as_ptr(db_context.get_store())`, a raw address —
[INFO] [stdout] //! not by anything that keeps that address meaningful on its own. Storing
[INFO] [stdout] //! only that `usize` without cloning the `Arc` would be unsound: if the
[INFO] [stdout] //! `DbContext` a guard was acquired from is dropped (its last other clone
[INFO] [stdout] //! going out of scope) while the guard still lives, the allocator is free to
[INFO] [stdout] //! reuse that exact address for an unrelated, brand-new `HashMapStore`. The
[INFO] [stdout] //! new store's *first* real write transaction would then find the address
[INFO] [stdout] //! already marked as held — tripping the guard for a store that never had a
[INFO] [stdout] //! concurrent writer — and worse, when the stale guard eventually dropped it
[INFO] [stdout] //! would remove the *new* store's entry, silently re-opening the door the
[INFO] [stdout] //! guard exists to close.
[INFO] [stdout] //!
[INFO] [stdout] //! [`WriteTransactionGuard`] therefore clones the `Arc<HashMapStore>` itself
[INFO] [stdout] //! (the `store` field below) and holds that clone for its entire lifetime, in
[INFO] [stdout] //! addition to using its address as the map key. Holding the clone keeps the
[INFO] [stdout] //! allocation alive — the address cannot be freed, let alone reused for a
[INFO] [stdout] //! different store, for as long as any guard referencing it exists. The
[INFO] [stdout] //! pointer is still fine to use as a `HashMap` key (it is still unique among
[INFO] [stdout] //! *currently live* stores); what changed is that the guard now makes that
[INFO] [stdout] //! uniqueness durable for its own lifetime instead of merely observing it at
[INFO] [stdout] //! acquire time.
[INFO] [stdout] //!
[INFO] [stdout] //! ## Diagnosing contention: naming both the current holder and the new claimant
[INFO] [stdout] //!
[INFO] [stdout] //! Each slot records not just "held", but who holds it: the owning thread's
[INFO] [stdout] //! [`ThreadId`] and name, plus the `site` string passed
[INFO] [stdout] //! to `acquire`. A contending `acquire` call reads that record before failing,
[INFO] [stdout] //! so the panic/error names **both** parties — "`{new_site}` on thread {new}
[INFO] [stdout] //! collided with `{holder_site}` still held by thread {holder}" — not just the
[INFO] [stdout] //! new claimant. That is enough to `grep` straight to the offending call site
[INFO] [stdout] //! without attaching a debugger, which is the entire point of a guard whose
[INFO] [stdout] //! job is to fail loudly instead of silently.
[INFO] [stdout] //!
[INFO] [stdout] //! This module deliberately does **not** add any machinery to reclaim a slot
[INFO] [stdout] //! left held by a leaked or `mem::forget`-ed guard (a timeout, a generation
[INFO] [stdout] //! counter, a "steal the lock" escape hatch). Two things make that
[INFO] [stdout] //! unnecessary rather than merely deferred:
[INFO] [stdout] //!
[INFO] [stdout] //! * The ABA fix above already rules out the scenario that would make a stale
[INFO] [stdout] //!   key *dangerous* — a leaked guard cannot cause a *different, live* store to
[INFO] [stdout] //!   be wrongly blocked, because it holds the `Arc` that keeps its own store's
[INFO] [stdout] //!   address from ever being handed to a different store while the leak
[INFO] [stdout] //!   persists. A leak only ever poisons the one store it actually leaked on.
[INFO] [stdout] //! * For that one store, "a write-transaction slot that is held and never
[INFO] [stdout] //!   released" and "a write transaction that is still legitimately in
[INFO] [stdout] //!   progress" are the same observable state from this module's point of view.
[INFO] [stdout] //!   Any reclaim heuristic (e.g. "assume held-for-N-seconds means leaked") is a
[INFO] [stdout] //!   race against a legitimately long transaction and would trade a loud,
[INFO] [stdout] //!   honest failure for an occasional silent one — reintroducing exactly the
[INFO] [stdout] //!   failure mode this guard exists to prevent. The recorded thread id/site
[INFO] [stdout] //!   above turn that failure into an immediately diagnosable one (which call
[INFO] [stdout] //!   site leaked it), which is the correct fix for a bug, not a runtime
[INFO] [stdout] //!   work-around for one.
[INFO] [stdout] //!
[INFO] [stdout] //! ## Re-entrant `begin_transaction` on the same unit of work is diagnosed honestly
[INFO] [stdout] //!
[INFO] [stdout] //! Every generated `CommandUnitOfWork::begin_transaction` follows the same
[INFO] [stdout] //! shape:
[INFO] [stdout] //!
[INFO] [stdout] //! ```ignore
[INFO] [stdout] //! self.write_guard = Some(WriteTransactionGuard::acquire(&self.context, "some_use_case")?);
[INFO] [stdout] //! ```
[INFO] [stdout] //!
[INFO] [stdout] //! Assignment evaluates its right-hand side — the whole `acquire` call —
[INFO] [stdout] //! *before* dropping whatever `self.write_guard` held previously. So if
[INFO] [stdout] //! `begin_transaction` were ever called again on the *same* `CommandUnitOfWork`
[INFO] [stdout] //! instance while its own previous guard is still `Some` (a re-entrant call, or
[INFO] [stdout] //! a retry loop that calls `begin_transaction` again without going through
[INFO] [stdout] //! `commit`/`rollback` first — both of which clear the field), the new
[INFO] [stdout] //! `acquire` runs while the old guard is still registered as held. Naively that
[INFO] [stdout] //! looks identical to a genuine second writer, and the resulting panic/error
[INFO] [stdout] //! would misidentify a single UoW talking to itself as two concurrent writers
[INFO] [stdout] //! — plausible-sounding, and wrong, which is worse than no diagnostic at all.
[INFO] [stdout] //!
[INFO] [stdout] //! `acquire` tells the two apart using exactly the holder record described
[INFO] [stdout] //! above: if the slot is already held **by the current thread**, the message
[INFO] [stdout] //! says so explicitly — "this thread already holds this store's slot,
[INFO] [stdout] //! acquired at `{holder_site}`, this looks like a re-entrant/retried
[INFO] [stdout] //! `begin_transaction`, not a second writer" — instead of the generic
[INFO] [stdout] //! cross-thread contention wording. It still fails (the underlying invariant
[INFO] [stdout] //! violation — two live guards on one store — is exactly as real as in the
[INFO] [stdout] //! cross-thread case, and this module cannot see into the caller's struct to
[INFO] [stdout] //! silently drop the stale one for it), but the diagnostic now points at the
[INFO] [stdout] //! actual bug (a UoW re-entering `begin_transaction` without clearing its own
[INFO] [stdout] //! previous guard) instead of a phantom concurrent writer.
[INFO] [stdout] //!
[INFO] [stdout] //! ## RAII — cannot be defeated by an early return or a panic
[INFO] [stdout] //!
[INFO] [stdout] //! [`WriteTransactionGuard`] is a plain owned struct with only a `Drop` impl to
[INFO] [stdout] //! release its slot; there is no separate "release" method to forget to call.
[INFO] [stdout] //! Binding it to a named local for the whole span a write transaction may be
[INFO] [stdout] //! open means the slot is freed on every exit path — the happy path, an early
[INFO] [stdout] //! `?` return, or an unwinding panic — exactly like `Transaction`'s own `Drop`
[INFO] [stdout] //! safety net that it sits beside.
[INFO] [stdout] 
[INFO] [stdout] use std::collections::HashMap;
[INFO] [stdout] use std::sync::{Arc, Mutex};
[INFO] [stdout] use std::thread::ThreadId;
[INFO] [stdout] 
[INFO] [stdout] use crate::database::db_context::DbContext;
[INFO] [stdout] use crate::database::hashmap_store::HashMapStore;
[INFO] [stdout] 
[INFO] [stdout] /// Who currently holds a store's write-transaction slot — enough to name them
[INFO] [stdout] /// in a contention panic/error (see the module doc's "Diagnosing contention"
[INFO] [stdout] /// section) instead of just saying "someone already holds it".
[INFO] [stdout] #[derive(Clone, Debug)]
[INFO] [stdout] struct Holder {
[INFO] [stdout]     thread_id: ThreadId,
[INFO] [stdout]     thread_name: Option<String>,
[INFO] [stdout]     site: &'static str,
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl Holder {
[INFO] [stdout]     fn describe(&self) -> String {
[INFO] [stdout]         match &self.thread_name {
[INFO] [stdout]             Some(name) => format!("{name:?} ({:?})", self.thread_id),
[INFO] [stdout]             None => format!("<unnamed> ({:?})", self.thread_id),
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] /// Which stores (keyed by `Arc<HashMapStore>` pointer identity) currently have a
[INFO] [stdout] /// write transaction in flight, and who holds each one. `None` until the first
[INFO] [stdout] /// acquire; lazily initialised rather than built from a const-unfriendly
[INFO] [stdout] /// `HashMap::new()` in a `static` initializer.
[INFO] [stdout] static WRITERS: Mutex<Option<HashMap<usize, Holder>>> = Mutex::new(None);
[INFO] [stdout] 
[INFO] [stdout] /// RAII token proving "I hold the write-transaction slot for this store." See
[INFO] [stdout] /// the module doc for the full rationale.
[INFO] [stdout] #[must_use = "the slot is released when this drops — binding it to `_` drops it \
[INFO] [stdout]               immediately and defeats the guard; bind it to a named local (e.g. \
[INFO] [stdout]               `write_guard`) that lives for the whole write-transaction span"]
[INFO] [stdout] #[derive(Debug)]
[INFO] [stdout] pub struct WriteTransactionGuard {
[INFO] [stdout]     key: usize,
[INFO] [stdout]     /// Never read directly — its only job is to keep the store's allocation
[INFO] [stdout]     /// (and hence `key`'s validity as a pointer identity) alive for as long as
[INFO] [stdout]     /// this guard exists. See the module doc's "Holding the `Arc`" section.
[INFO] [stdout]     #[allow(dead_code)]
[INFO] [stdout]     store: Arc<HashMapStore>,
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl WriteTransactionGuard {
[INFO] [stdout]     /// Claim the write-transaction slot for `db_context`'s store. `site` names
[INFO] [stdout]     /// the call site, used only in the panic/error message.
[INFO] [stdout]     ///
[INFO] [stdout]     /// # Panics
[INFO] [stdout]     /// In debug builds, panics immediately if this store's slot is already
[INFO] [stdout]     /// held — some other write transaction is already open on it. Turns the
[INFO] [stdout]     /// silent-data-loss failure mode this guards against into a loud one, at
[INFO] [stdout]     /// the exact call site that introduced the second writer (or, for a
[INFO] [stdout]     /// same-thread re-entry, the call site that never released its own
[INFO] [stdout]     /// previous guard — see the module doc).
[INFO] [stdout]     ///
[INFO] [stdout]     /// # Errors
[INFO] [stdout]     /// In release builds, returns an error instead of panicking — a shipped
[INFO] [stdout]     /// build should degrade to a reported failure, not abort the process.
[INFO] [stdout]     pub fn acquire(db_context: &DbContext, site: &'static str) -> anyhow::Result<Self> {
[INFO] [stdout]         // Clone (not just dereference) the `Arc`: the guard keeps this clone
[INFO] [stdout]         // alive for its whole lifetime so `key` can never be handed out to a
[INFO] [stdout]         // different store by the allocator while a guard referencing it
[INFO] [stdout]         // exists. See the module doc's "Holding the `Arc`" section.
[INFO] [stdout]         let store = Arc::clone(db_context.get_store());
[INFO] [stdout]         let key = Arc::as_ptr(&store) as usize;
[INFO] [stdout] 
[INFO] [stdout]         let current_thread = std::thread::current();
[INFO] [stdout]         let current_thread_id = current_thread.id();
[INFO] [stdout]         let current_thread_name = current_thread.name().map(str::to_owned);
[INFO] [stdout] 
[INFO] [stdout]         // Take the lock, mutate, and drop it again *before* possibly panicking —
[INFO] [stdout]         // panicking while holding a `std::sync::Mutex` poisons it for every
[INFO] [stdout]         // other store's acquire/drop for the rest of the process, which would
[INFO] [stdout]         // turn one store's legitimate bug into spurious failures for every
[INFO] [stdout]         // other, wholly unrelated store.
[INFO] [stdout]         let existing_holder = {
[INFO] [stdout]             let mut writers = WRITERS.lock().unwrap_or_else(|e| e.into_inner());
[INFO] [stdout]             let map = writers.get_or_insert_with(HashMap::new);
[INFO] [stdout]             match map.get(&key) {
[INFO] [stdout]                 Some(holder) => Some(holder.clone()),
[INFO] [stdout]                 None => {
[INFO] [stdout]                     map.insert(
[INFO] [stdout]                         key,
[INFO] [stdout]                         Holder {
[INFO] [stdout]                             thread_id: current_thread_id,
[INFO] [stdout]                             thread_name: current_thread_name,
[INFO] [stdout]                             site,
[INFO] [stdout]                         },
[INFO] [stdout]                     );
[INFO] [stdout]                     None
[INFO] [stdout]                 }
[INFO] [stdout]             }
[INFO] [stdout]         };
[INFO] [stdout] 
[INFO] [stdout]         if let Some(holder) = existing_holder {
[INFO] [stdout]             let new_thread_desc = current_thread.name().map_or_else(
[INFO] [stdout]                 || format!("{current_thread_id:?}"),
[INFO] [stdout]                 |n| format!("{n:?} ({current_thread_id:?})"),
[INFO] [stdout]             );
[INFO] [stdout] 
[INFO] [stdout]             let message = if holder.thread_id == current_thread_id {
[INFO] [stdout]                 format!(
[INFO] [stdout]                     "WriteTransactionGuard: `{site}` (thread {new_thread_desc}) tried to open a \
[INFO] [stdout]                      write transaction on a store THIS SAME THREAD already holds the slot for — \
[INFO] [stdout]                      acquired earlier at `{holder_site}` and never released. This is NOT a \
[INFO] [stdout]                      second concurrent writer; it is a re-entrant or retried `begin_transaction` \
[INFO] [stdout]                      on the same (or a nested) unit of work whose previous \
[INFO] [stdout]                      `WriteTransactionGuard` was never dropped — either a missing \
[INFO] [stdout]                      commit()/rollback() before the retry, or `self.write_guard = Some(new)` \
[INFO] [stdout]                      built `new` (running `acquire` again) before the old guard's `Drop` had a \
[INFO] [stdout]                      chance to run. Drop the previous guard (commit/rollback it, or explicitly \
[INFO] [stdout]                      `self.write_guard = None;`) before acquiring a new one.",
[INFO] [stdout]                     holder_site = holder.site,
[INFO] [stdout]                 )
[INFO] [stdout]             } else {
[INFO] [stdout]                 format!(
[INFO] [stdout]                     "WriteTransactionGuard: a second write transaction started at `{site}` \
[INFO] [stdout]                      (thread {new_thread_desc}) while another was already open on this store, \
[INFO] [stdout]                      held by thread {holder} since `{holder_site}`. \
[INFO] [stdout]                      Transaction::begin_write_transaction takes a WHOLE-STORE savepoint \
[INFO] [stdout]                      (common/src/database/transactions.rs) — a second concurrent writer's \
[INFO] [stdout]                      rollback/Drop would silently erase the first writer's committed edits. \
[INFO] [stdout]                      Fix the caller; do not remove or weaken this guard.",
[INFO] [stdout]                     holder = holder.describe(),
[INFO] [stdout]                     holder_site = holder.site,
[INFO] [stdout]                 )
[INFO] [stdout]             };
[INFO] [stdout] 
[INFO] [stdout]             if cfg!(debug_assertions) {
[INFO] [stdout]                 panic!("{message}");
[INFO] [stdout]             }
[INFO] [stdout]             anyhow::bail!(message);
[INFO] [stdout]         }
[INFO] [stdout] 
[INFO] [stdout]         Ok(WriteTransactionGuard { key, store })
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl Drop for WriteTransactionGuard {
[INFO] [stdout]     fn drop(&mut self) {
[INFO] [stdout]         if let Some(map) = WRITERS.lock().unwrap_or_else(|e| e.into_inner()).as_mut() {
[INFO] [stdout]             map.remove(&self.key);
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] #[cfg(test)]
[INFO] [stdout] mod tests {
[INFO] [stdout]     use super::*;
[INFO] [stdout] 
[INFO] [stdout]     fn ctx() -> DbContext {
[INFO] [stdout]         DbContext::new().unwrap()
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Extracts the panic payload as a string regardless of whether it was
[INFO] [stdout]     /// boxed as `&'static str` (a literal) or `String` (an interpolated
[INFO] [stdout]     /// `format!`/`panic!("{message}")` payload, which is what this module's
[INFO] [stdout]     /// contention messages are).
[INFO] [stdout]     fn panic_message(payload: &(dyn std::any::Any + Send)) -> String {
[INFO] [stdout]         if let Some(s) = payload.downcast_ref::<&str>() {
[INFO] [stdout]             (*s).to_string()
[INFO] [stdout]         } else if let Some(s) = payload.downcast_ref::<String>() {
[INFO] [stdout]             s.clone()
[INFO] [stdout]         } else {
[INFO] [stdout]             panic!("panic payload was neither &str nor String");
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     /// Drive a contending `acquire` and return its failure message, whichever
[INFO] [stdout]     /// way this build reports failure.
[INFO] [stdout]     ///
[INFO] [stdout]     /// `acquire` deliberately fails *differently* per profile — it panics
[INFO] [stdout]     /// under `debug_assertions` and returns an error without them — while
[INFO] [stdout]     /// producing an identical message either way. Tests that hard-coded the
[INFO] [stdout]     /// debug behaviour would fail the moment a generated app ran
[INFO] [stdout]     /// `cargo test --release`, and since this module is generated, a
[INFO] [stdout]     /// downstream developer could not fix those tests without their edit
[INFO] [stdout]     /// being erased by the next `qleany generate`. Absorbing the difference
[INFO] [stdout]     /// here keeps the suite green under both profiles and, more usefully,
[INFO] [stdout]     /// means the release path is genuinely exercised rather than assumed.
[INFO] [stdout]     fn contention_message(db: &DbContext, site: &'static str) -> String {
[INFO] [stdout]         let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
[INFO] [stdout]             WriteTransactionGuard::acquire(db, site)
[INFO] [stdout]         }));
[INFO] [stdout] 
[INFO] [stdout]         match outcome {
[INFO] [stdout]             Err(payload) => {
[INFO] [stdout]                 // `if !cfg!(..) { panic!(..) }` rather than `assert!(cfg!(..), ..)`:
[INFO] [stdout]                 // the condition is a compile-time constant, which clippy's
[INFO] [stdout]                 // `assertions_on_constants` rejects under `-D warnings`. The
[INFO] [stdout]                 // check is worth keeping — it is what proves the profile and the
[INFO] [stdout]                 // observed behaviour agree — so it is restated, not removed.
[INFO] [stdout]                 if !cfg!(debug_assertions) {
[INFO] [stdout]                     panic!(
[INFO] [stdout]                         "`{site}`: a release build must report contention by returning an \
[INFO] [stdout]                          error, not by panicking"
[INFO] [stdout]                     );
[INFO] [stdout]                 }
[INFO] [stdout]                 panic_message(payload.as_ref())
[INFO] [stdout]             }
[INFO] [stdout]             Ok(Err(error)) => {
[INFO] [stdout]                 if cfg!(debug_assertions) {
[INFO] [stdout]                     panic!(
[INFO] [stdout]                         "`{site}`: a debug build must report contention by panicking loudly \
[INFO] [stdout]                          at the offending call site, not by returning an error"
[INFO] [stdout]                     );
[INFO] [stdout]                 }
[INFO] [stdout]                 format!("{error}")
[INFO] [stdout]             }
[INFO] [stdout]             Ok(Ok(_)) => panic!(
[INFO] [stdout]                 "`{site}` acquired a slot that was already held — the guard did not fire \
[INFO] [stdout]                  at all, which is the exact silent failure it exists to prevent"
[INFO] [stdout]             ),
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     #[test]
[INFO] [stdout]     fn sequential_acquire_release_never_trips_the_guard() {
[INFO] [stdout]         let db = ctx();
[INFO] [stdout]         for _ in 0..3 {
[INFO] [stdout]             let g = WriteTransactionGuard::acquire(&db, "test").unwrap();
[INFO] [stdout]             drop(g);
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     #[test]
[INFO] [stdout]     fn two_different_stores_can_each_acquire_independently() {
[INFO] [stdout]         let a = ctx();
[INFO] [stdout]         let b = ctx();
[INFO] [stdout]         let _ga = WriteTransactionGuard::acquire(&a, "a").unwrap();
[INFO] [stdout]         let _gb = WriteTransactionGuard::acquire(&b, "b").unwrap();
[INFO] [stdout]         // No panic reaching here is the assertion: two unrelated stores must
[INFO] [stdout]         // never contend on each other's slot.
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     #[test]
[INFO] [stdout]     fn dropping_the_first_guard_frees_the_slot_for_a_second_acquire() {
[INFO] [stdout]         let db = ctx();
[INFO] [stdout]         let first = WriteTransactionGuard::acquire(&db, "first").unwrap();
[INFO] [stdout]         drop(first);
[INFO] [stdout]         let second = WriteTransactionGuard::acquire(&db, "second");
[INFO] [stdout]         assert!(second.is_ok(), "the slot must be free once the first guard dropped");
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     #[test]
[INFO] [stdout]     fn a_second_concurrent_acquire_on_the_same_store_fails() {
[INFO] [stdout]         let db = ctx();
[INFO] [stdout]         let _first = WriteTransactionGuard::acquire(&db, "first").unwrap();
[INFO] [stdout]         let message = contention_message(&db, "second");
[INFO] [stdout]         assert!(
[INFO] [stdout]             message.contains("second"),
[INFO] [stdout]             "message must name the call site that was refused: {message}"
[INFO] [stdout]         );
[INFO] [stdout] 
[INFO] [stdout]         // The failure must not have poisoned the global map for anyone else: a
[INFO] [stdout]         // fresh, unrelated store can still acquire immediately afterward.
[INFO] [stdout]         let other = ctx();
[INFO] [stdout]         assert!(WriteTransactionGuard::acquire(&other, "other").is_ok());
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     #[test]
[INFO] [stdout]     fn the_guard_keeps_the_store_allocation_alive_after_the_db_context_drops() {
[INFO] [stdout]         let db = ctx();
[INFO] [stdout]         let store_ref = Arc::clone(db.get_store());
[INFO] [stdout]         // `db` itself + our `store_ref` clone.
[INFO] [stdout]         assert_eq!(Arc::strong_count(&store_ref), 2);
[INFO] [stdout] 
[INFO] [stdout]         let guard = WriteTransactionGuard::acquire(&db, "test").unwrap();
[INFO] [stdout]         // Acquiring must have taken its own clone: three owners now.
[INFO] [stdout]         assert_eq!(
[INFO] [stdout]             Arc::strong_count(&store_ref),
[INFO] [stdout]             3,
[INFO] [stdout]             "WriteTransactionGuard::acquire must clone the store's Arc, not just read its pointer"
[INFO] [stdout]         );
[INFO] [stdout] 
[INFO] [stdout]         drop(db);
[INFO] [stdout]         // With the DbContext gone, only the guard's clone (+ ours) keep the
[INFO] [stdout]         // allocation alive. If the guard did NOT hold a clone, this would
[INFO] [stdout]         // drop to 1 and the address would become free for the allocator to
[INFO] [stdout]         // hand to an unrelated, brand-new store — the ABA scenario the
[INFO] [stdout]         // module doc describes.
[INFO] [stdout]         assert_eq!(
[INFO] [stdout]             Arc::strong_count(&store_ref),
[INFO] [stdout]             2,
[INFO] [stdout]             "the guard must still be holding the store alive once the DbContext drops"
[INFO] [stdout]         );
[INFO] [stdout] 
[INFO] [stdout]         drop(guard);
[INFO] [stdout]         assert_eq!(
[INFO] [stdout]             Arc::strong_count(&store_ref),
[INFO] [stdout]             1,
[INFO] [stdout]             "the guard's clone must be released when the guard drops"
[INFO] [stdout]         );
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     #[test]
[INFO] [stdout]     fn cross_thread_contention_names_both_the_holder_and_the_new_claimant() {
[INFO] [stdout]         let db = ctx();
[INFO] [stdout]         let _first = WriteTransactionGuard::acquire(&db, "first_site_xyz").unwrap();
[INFO] [stdout] 
[INFO] [stdout]         let db_for_thread = db.clone();
[INFO] [stdout]         let handle = std::thread::Builder::new()
[INFO] [stdout]             .name("contender-thread".to_string())
[INFO] [stdout]             .spawn(move || contention_message(&db_for_thread, "second_site_xyz"))
[INFO] [stdout]             .unwrap();
[INFO] [stdout] 
[INFO] [stdout]         let message = handle
[INFO] [stdout]             .join()
[INFO] [stdout]             .expect("the contending thread must fail through the guard, not around it");
[INFO] [stdout] 
[INFO] [stdout]         assert!(
[INFO] [stdout]             message.contains("first_site_xyz"),
[INFO] [stdout]             "message must name the current holder's call site: {message}"
[INFO] [stdout]         );
[INFO] [stdout]         assert!(
[INFO] [stdout]             message.contains("second_site_xyz"),
[INFO] [stdout]             "message must name the new claimant's call site: {message}"
[INFO] [stdout]         );
[INFO] [stdout]         assert!(
[INFO] [stdout]             message.contains("contender-thread"),
[INFO] [stdout]             "message must name the new claimant's thread: {message}"
[INFO] [stdout]         );
[INFO] [stdout]         assert!(
[INFO] [stdout]             !message.contains("SAME THREAD"),
[INFO] [stdout]             "a genuine cross-thread writer must not be misdiagnosed as same-thread \
[INFO] [stdout]              re-entry: {message}"
[INFO] [stdout]         );
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     #[test]
[INFO] [stdout]     fn same_thread_reacquire_is_diagnosed_as_self_reentry_not_a_phantom_second_writer() {
[INFO] [stdout]         let db = ctx();
[INFO] [stdout]         let _first = WriteTransactionGuard::acquire(&db, "begin_transaction_first_call").unwrap();
[INFO] [stdout] 
[INFO] [stdout]         let message = contention_message(&db, "begin_transaction_retry");
[INFO] [stdout] 
[INFO] [stdout]         assert!(
[INFO] [stdout]             message.contains("begin_transaction_first_call"),
[INFO] [stdout]             "message must name the site that still holds the slot: {message}"
[INFO] [stdout]         );
[INFO] [stdout]         assert!(
[INFO] [stdout]             message.contains("begin_transaction_retry"),
[INFO] [stdout]             "message must name the re-entrant call site: {message}"
[INFO] [stdout]         );
[INFO] [stdout]         assert!(
[INFO] [stdout]             message.contains("SAME THREAD") || message.contains("re-entrant"),
[INFO] [stdout]             "message must say this is the same thread re-entering, not blame a second \
[INFO] [stdout]              writer: {message}"
[INFO] [stdout]         );
[INFO] [stdout]         assert!(
[INFO] [stdout]             !message.contains("Fix the caller; do not remove or weaken this guard"),
[INFO] [stdout]             "must not reuse the generic cross-thread-second-writer wording for a \
[INFO] [stdout]              same-thread self re-entry: {message}"
[INFO] [stdout]         );
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] stack backtrace:
[INFO] [stdout]    0:     0x5a49745cc881 - <<std[617be2ca0a6fd877]::sys::backtrace::BacktraceLock>::print::DisplayBacktrace as core[a373ad73a504971]::fmt::Display>::fmt
[INFO] [stdout]    1:     0x5a49745e469a - core[a373ad73a504971]::fmt::write
[INFO] [stdout]    2:     0x5a49745d10dc - <alloc[2e1b2a31b91974c5]::vec::Vec<u8> as core[a373ad73a504971]::io::write::Write>::write_fmt
[INFO] [stdout]    3:     0x5a49745a75d6 - std[617be2ca0a6fd877]::panicking::default_hook::{closure#0}
[INFO] [stdout]    4:     0x5a49745c4669 - std[617be2ca0a6fd877]::panicking::default_hook
[INFO] [stdout]    5:     0x5a497438fc20 - test[aada8e7e76ba119b]::test_main_inner::{closure#0}
[INFO] [stdout]    6:     0x5a49745c4822 - std[617be2ca0a6fd877]::panicking::panic_with_hook
[INFO] [stdout]    7:     0x5a49745a7682 - std[617be2ca0a6fd877]::panicking::panic_handler::{closure#0}
[INFO] [stdout]    8:     0x5a497459fc79 - std[617be2ca0a6fd877]::sys::backtrace::__rust_end_short_backtrace::<std[617be2ca0a6fd877]::panicking::panic_handler::{closure#0}, !>
[INFO] [stdout]    9:     0x5a49745a839d - __rustc[ea4f61d10b84890b]::rust_begin_unwind
[INFO] [stdout]   10:     0x5a49745e4ebc - core[a373ad73a504971]::panicking::panic_fmt
[INFO] [stdout]   11:     0x5a497430eb66 - rust_file_generation[bc330dc3ad16d42f]::use_cases::common::rust_code_generator::rust_code_generator_tests::assert_is_valid_rust
[INFO] [stdout]   12:     0x5a497431c3c7 - <rust_file_generation[bc330dc3ad16d42f]::use_cases::common::rust_code_generator::rust_code_generator_tests::write_guard_template_renders_valid_rust::{closure#0} as core[a373ad73a504971]::ops::function::FnOnce<()>>::call_once
[INFO] [stdout]   13:     0x5a497438306b - test[aada8e7e76ba119b]::__rust_begin_short_backtrace::<core[a373ad73a504971]::result::Result<(), alloc[2e1b2a31b91974c5]::string::String>, fn() -> core[a373ad73a504971]::result::Result<(), alloc[2e1b2a31b91974c5]::string::String>>
[INFO] [stdout]   14:     0x5a4974390245 - test[aada8e7e76ba119b]::run_test::{closure#0}
[INFO] [stdout]   15:     0x5a4974389b34 - std[617be2ca0a6fd877]::sys::backtrace::__rust_begin_short_backtrace::<test[aada8e7e76ba119b]::run_test::{closure#1}, ()>
[INFO] [stdout]   16:     0x5a49743932d2 - <std[617be2ca0a6fd877]::thread::lifecycle::spawn_unchecked<test[aada8e7e76ba119b]::run_test::{closure#1}, ()>::{closure#1} as core[a373ad73a504971]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0}
[INFO] [stdout]   17:     0x5a49745cba6f - <std[617be2ca0a6fd877]::sys::thread::unix::Thread>::new::thread_start
[INFO] [stdout]   18:     0x78bb6f0cbdfa - <unknown>
[INFO] [stdout]   19:     0x78bb6f15f3d4 - clone
[INFO] [stdout]   20:                0x0 - <unknown>
[INFO] [stdout] 
[INFO] [stdout] ---- use_cases::common::rust_code_generator::rust_code_generator_tests::feature_use_case_uow_long_operation_write_template_wires_the_guard_and_renders_valid_rust stdout ----
[INFO] [stdout] 
[INFO] [stdout] thread 'use_cases::common::rust_code_generator::rust_code_generator_tests::feature_use_case_uow_long_operation_write_template_wires_the_guard_and_renders_valid_rust' (1227) panicked at src/use_cases/common/rust_code_generator/rust_code_generator_tests.rs:830:5:
[INFO] [stdout] feature_use_case_uow (write, long-operation): rendered template output is not valid Rust:
[INFO] [stdout] error: 'rustfmt' is not installed for the toolchain '1.100.0-beta.1-x86_64-unknown-linux-gnu'.
[INFO] [stdout] help: run `rustup component add --toolchain 1.100.0-beta.1-x86_64-unknown-linux-gnu rustfmt` to install it
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] --- rendered code ---
[INFO] [stdout] // Generated by Qleany v1.0.0 from feature_use_case_uow.tera
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] use crate::use_cases::do_thing_uc::{
[INFO] [stdout]     DoThingUnitOfWorkFactoryTrait, DoThingUnitOfWorkTrait,
[INFO] [stdout] };
[INFO] [stdout] use anyhow::{Ok, Result};
[INFO] [stdout] use common::database::{db_context::DbContext, transactions::Transaction};
[INFO] [stdout] #[allow(unused_imports)]
[INFO] [stdout] use common::entities:: {
[INFO] [stdout] Thing,
[INFO] [stdout] };
[INFO] [stdout] use std::sync::Arc;
[INFO] [stdout] use common::database::CommandUnitOfWork;
[INFO] [stdout] use common::database::write_guard::WriteTransactionGuard;
[INFO] [stdout] use common::event::{AllEvent, DirectAccessEntity, Event, EventBuffer, EventHub, Origin};
[INFO] [stdout] use common::event::DoingEvent::DoThing;
[INFO] [stdout] #[allow(unused_imports)]
[INFO] [stdout] use common::types;
[INFO] [stdout] #[allow(unused_imports)]
[INFO] [stdout] use common::types::EntityId;
[INFO] [stdout] use std::sync::Mutex;
[INFO] [stdout] 
[INFO] [stdout] pub struct DoThingUnitOfWork {
[INFO] [stdout]     context: DbContext,
[INFO] [stdout]     transaction: Mutex<Option<Transaction>>,
[INFO] [stdout]     event_hub: Arc<EventHub>,
[INFO] [stdout]     // RAII: acquired in `begin_transaction`, released via `commit`/`rollback`
[INFO] [stdout]     // clearing it — see `common::database::write_guard`'s module doc for the
[INFO] [stdout]     // single-write-transaction-per-store invariant this enforces.
[INFO] [stdout]     write_guard: Mutex<Option<WriteTransactionGuard>>,
[INFO] [stdout]     event_buffer: Mutex<EventBuffer>,
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl DoThingUnitOfWork {
[INFO] [stdout]     pub fn new(db_context: &DbContext, event_hub: &Arc<EventHub>) -> Self {
[INFO] [stdout]         DoThingUnitOfWork {
[INFO] [stdout]             context: db_context.clone(),
[INFO] [stdout]             transaction: Mutex::new(None),
[INFO] [stdout]             write_guard: Mutex::new(None),
[INFO] [stdout]             event_hub: event_hub.clone(),
[INFO] [stdout]             event_buffer: Mutex::new(EventBuffer::new()),
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl CommandUnitOfWork for DoThingUnitOfWork {
[INFO] [stdout]     fn begin_transaction(&mut self) -> Result<()> {
[INFO] [stdout]         // Acquire BEFORE the write transaction itself — see
[INFO] [stdout]         // `common::database::write_guard`'s module doc.
[INFO] [stdout]         *common::long_operation::lock_or_recover(&self.write_guard) = Some(WriteTransactionGuard::acquire(&self.context, "do_thing")?);
[INFO] [stdout]         let mut transaction = common::long_operation::lock_or_recover(&self.transaction);
[INFO] [stdout]         *transaction = Some(Transaction::begin_write_transaction(&self.context)?);
[INFO] [stdout]         common::long_operation::lock_or_recover(&self.event_buffer).begin_buffering();
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn commit(&mut self) -> Result<()> {
[INFO] [stdout]         let mut transaction = common::long_operation::lock_or_recover(&self.transaction);
[INFO] [stdout]         transaction.take()
[INFO] [stdout]             .ok_or_else(|| anyhow::anyhow!("No active transaction"))?
[INFO] [stdout]             .commit()?;
[INFO] [stdout]         drop(transaction); // release lock before flushing events
[INFO] [stdout]         *common::long_operation::lock_or_recover(&self.write_guard) = None;
[INFO] [stdout]         for event in common::long_operation::lock_or_recover(&self.event_buffer).flush() {
[INFO] [stdout]             self.event_hub.send_event(event);
[INFO] [stdout]         }
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn rollback(&mut self) -> Result<()> {
[INFO] [stdout]         let mut transaction = common::long_operation::lock_or_recover(&self.transaction);
[INFO] [stdout]         transaction.take()
[INFO] [stdout]             .ok_or_else(|| anyhow::anyhow!("No active transaction"))?
[INFO] [stdout]             .rollback()?;
[INFO] [stdout]         drop(transaction);
[INFO] [stdout]         *common::long_operation::lock_or_recover(&self.write_guard) = None;
[INFO] [stdout]         common::long_operation::lock_or_recover(&self.event_buffer).discard();
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn create_savepoint(&self) -> Result<types::Savepoint> {
[INFO] [stdout]         let transaction = common::long_operation::lock_or_recover(&self.transaction);
[INFO] [stdout]         transaction.as_ref()
[INFO] [stdout]             .ok_or_else(|| anyhow::anyhow!("No active transaction"))?
[INFO] [stdout]             .create_savepoint()
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn restore_to_savepoint(&mut self, savepoint: types::Savepoint) -> Result<()> {
[INFO] [stdout]         let mut transaction_guard = common::long_operation::lock_or_recover(&self.transaction);
[INFO] [stdout]         let mut transaction = transaction_guard.take()
[INFO] [stdout]             .ok_or_else(|| anyhow::anyhow!("No active transaction"))?;
[INFO] [stdout]         transaction.restore_to_savepoint(savepoint)?;
[INFO] [stdout] 
[INFO] [stdout]         // Discard buffered events — savepoint restore invalidated them
[INFO] [stdout]         common::long_operation::lock_or_recover(&self.event_buffer).discard();
[INFO] [stdout] 
[INFO] [stdout]         // Send Reset immediately (not buffered — UI must refresh now)
[INFO] [stdout]         self.event_hub.send_event(Event {
[INFO] [stdout]             origin: Origin::DirectAccess(DirectAccessEntity::All(AllEvent::Reset)),
[INFO] [stdout]             ids: vec![],
[INFO] [stdout]             data: None,
[INFO] [stdout]         });
[INFO] [stdout] 
[INFO] [stdout]         *transaction_guard = Some(transaction);
[INFO] [stdout] 
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] //TODO: adapt entities and actions to real use :
[INFO] [stdout] // Create, CreateMulti, Get, GetMulti, Update (scalar-only), UpdateMulti (scalar-only),
[INFO] [stdout] // UpdateWithRelationships, UpdateWithRelationshipsMulti,
[INFO] [stdout] // Remove, RemoveMulti, GetRelationship, GetRelationshipsFromRightIds,
[INFO] [stdout] // SetRelationship, SetRelationshipMulti, Snapshot, Restore
[INFO] [stdout] //
[INFO] [stdout] // You have here a long operation read-write unit of work.
[INFO] [stdout] //
[INFO] [stdout] // RO means Read Only, so *RO actions should not used be here.
[INFO] [stdout] // Don't forget to set thread_safe = true for long operation's unit of work.
[INFO] [stdout] // Do not mix read-only and write actions in the same unit of work.
[INFO] [stdout] //
[INFO] [stdout] // Exactly the same macros must be set in the use case uow trait file in ../use_cases/do_thing_uc.rs
[INFO] [stdout] //
[INFO] [stdout] #[macros::uow_action(entity = "Thing", action = "Get", thread_safe = true)]
[INFO] [stdout] #[macros::uow_action(entity = "Thing", action = "GetMulti", thread_safe = true)]
[INFO] [stdout] #[macros::uow_action(entity = "Thing", action = "Snapshot", thread_safe = true)]
[INFO] [stdout] #[macros::uow_action(entity = "Thing", action = "Restore", thread_safe = true)]
[INFO] [stdout] impl DoThingUnitOfWorkTrait for DoThingUnitOfWork {
[INFO] [stdout]     fn publish_do_thing_event(&self, ids: Vec<EntityId>, data: Option<String>) {
[INFO] [stdout]         self.event_hub.send_event(Event {
[INFO] [stdout]             origin: Origin::Doing(DoThing),
[INFO] [stdout]             ids,
[INFO] [stdout]             data,
[INFO] [stdout]         });
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] pub struct DoThingUnitOfWorkFactory {
[INFO] [stdout]     context: DbContext,
[INFO] [stdout]     event_hub: Arc<EventHub>,
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl DoThingUnitOfWorkFactory {
[INFO] [stdout]     pub fn new(db_context: &DbContext, event_hub: &Arc<EventHub>) -> Self {
[INFO] [stdout]         DoThingUnitOfWorkFactory {
[INFO] [stdout]             context: db_context.clone(),
[INFO] [stdout]             event_hub: event_hub.clone(),
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl DoThingUnitOfWorkFactoryTrait for DoThingUnitOfWorkFactory {
[INFO] [stdout]     fn create(&self) -> Box<dyn DoThingUnitOfWorkTrait> {
[INFO] [stdout]         Box::new(DoThingUnitOfWork::new(&self.context, &self.event_hub))
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] stack backtrace:
[INFO] [stdout]    0:     0x5a49745cc881 - <<std[617be2ca0a6fd877]::sys::backtrace::BacktraceLock>::print::DisplayBacktrace as core[a373ad73a504971]::fmt::Display>::fmt
[INFO] [stdout]    1:     0x5a49745e469a - core[a373ad73a504971]::fmt::write
[INFO] [stdout]    2:     0x5a49745d10dc - <alloc[2e1b2a31b91974c5]::vec::Vec<u8> as core[a373ad73a504971]::io::write::Write>::write_fmt
[INFO] [stdout]    3:     0x5a49745a75d6 - std[617be2ca0a6fd877]::panicking::default_hook::{closure#0}
[INFO] [stdout]    4:     0x5a49745c4669 - std[617be2ca0a6fd877]::panicking::default_hook
[INFO] [stdout]    5:     0x5a497438fc20 - test[aada8e7e76ba119b]::test_main_inner::{closure#0}
[INFO] [stdout]    6:     0x5a49745c4822 - std[617be2ca0a6fd877]::panicking::panic_with_hook
[INFO] [stdout]    7:     0x5a49745a7682 - std[617be2ca0a6fd877]::panicking::panic_handler::{closure#0}
[INFO] [stdout]    8:     0x5a497459fc79 - std[617be2ca0a6fd877]::sys::backtrace::__rust_end_short_backtrace::<std[617be2ca0a6fd877]::panicking::panic_handler::{closure#0}, !>
[INFO] [stdout]    9:     0x5a49745a839d - __rustc[ea4f61d10b84890b]::rust_begin_unwind
[INFO] [stdout]   10:     0x5a49745e4ebc - core[a373ad73a504971]::panicking::panic_fmt
[INFO] [stdout]   11:     0x5a497430eb66 - rust_file_generation[bc330dc3ad16d42f]::use_cases::common::rust_code_generator::rust_code_generator_tests::assert_is_valid_rust
[INFO] [stdout]   12:     0x5a4974321c0e - <rust_file_generation[bc330dc3ad16d42f]::use_cases::common::rust_code_generator::rust_code_generator_tests::feature_use_case_uow_long_operation_write_template_wires_the_guard_and_renders_valid_rust::{closure#0} as core[a373ad73a504971]::ops::function::FnOnce<()>>::call_once
[INFO] [stdout]   13:     0x5a497438306b - test[aada8e7e76ba119b]::__rust_begin_short_backtrace::<core[a373ad73a504971]::result::Result<(), alloc[2e1b2a31b91974c5]::string::String>, fn() -> core[a373ad73a504971]::result::Result<(), alloc[2e1b2a31b91974c5]::string::String>>
[INFO] [stdout]   14:     0x5a4974390245 - test[aada8e7e76ba119b]::run_test::{closure#0}
[INFO] [stdout]   15:     0x5a4974389b34 - std[617be2ca0a6fd877]::sys::backtrace::__rust_begin_short_backtrace::<test[aada8e7e76ba119b]::run_test::{closure#1}, ()>
[INFO] [stdout]   16:     0x5a49743932d2 - <std[617be2ca0a6fd877]::thread::lifecycle::spawn_unchecked<test[aada8e7e76ba119b]::run_test::{closure#1}, ()>::{closure#1} as core[a373ad73a504971]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0}
[INFO] [stdout]   17:     0x5a49745cba6f - <std[617be2ca0a6fd877]::sys::thread::unix::Thread>::new::thread_start
[INFO] [stdout]   18:     0x78bb6f0cbdfa - <unknown>
[INFO] [stdout]   19:     0x78bb6f15f3d4 - clone
[INFO] [stdout]   20:                0x0 - <unknown>
[INFO] [stdout] 
[INFO] [stdout] ---- use_cases::common::rust_code_generator::rust_code_generator_tests::feature_use_case_uow_long_operation_read_only_template_has_no_guard_and_renders_valid_rust stdout ----
[INFO] [stdout] 
[INFO] [stdout] thread 'use_cases::common::rust_code_generator::rust_code_generator_tests::feature_use_case_uow_long_operation_read_only_template_has_no_guard_and_renders_valid_rust' (1226) panicked at src/use_cases/common/rust_code_generator/rust_code_generator_tests.rs:830:5:
[INFO] [stdout] feature_use_case_uow (read-only, long-operation): rendered template output is not valid Rust:
[INFO] [stdout] error: 'rustfmt' is not installed for the toolchain '1.100.0-beta.1-x86_64-unknown-linux-gnu'.
[INFO] [stdout] help: run `rustup component add --toolchain 1.100.0-beta.1-x86_64-unknown-linux-gnu rustfmt` to install it
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] --- rendered code ---
[INFO] [stdout] // Generated by Qleany v1.0.0 from feature_use_case_uow.tera
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] use crate::use_cases::do_thing_uc::{
[INFO] [stdout]     DoThingUnitOfWorkFactoryTrait, DoThingUnitOfWorkTrait,
[INFO] [stdout] };
[INFO] [stdout] use anyhow::{Ok, Result};
[INFO] [stdout] use common::database::{db_context::DbContext, transactions::Transaction};
[INFO] [stdout] #[allow(unused_imports)]
[INFO] [stdout] use common::entities:: {
[INFO] [stdout] Thing,
[INFO] [stdout] };
[INFO] [stdout] use common::database::QueryUnitOfWork;
[INFO] [stdout] use std::sync::Arc;
[INFO] [stdout] use common::event::{Event, EventHub, Origin};
[INFO] [stdout] use common::event::DoingEvent::DoThing;
[INFO] [stdout] #[allow(unused_imports)]
[INFO] [stdout] use common::types;
[INFO] [stdout] #[allow(unused_imports)]
[INFO] [stdout] use common::types::EntityId;
[INFO] [stdout] use std::sync::Mutex;
[INFO] [stdout] 
[INFO] [stdout] pub struct DoThingUnitOfWork {
[INFO] [stdout]     context: DbContext,
[INFO] [stdout]     transaction: Mutex<Option<Transaction>>,
[INFO] [stdout]     event_hub: Arc<EventHub>,
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl DoThingUnitOfWork {
[INFO] [stdout]     pub fn new(db_context: &DbContext, event_hub: &Arc<EventHub>) -> Self {
[INFO] [stdout]         DoThingUnitOfWork {
[INFO] [stdout]             context: db_context.clone(),
[INFO] [stdout]             transaction: Mutex::new(None),
[INFO] [stdout]             event_hub: event_hub.clone(),
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl QueryUnitOfWork for DoThingUnitOfWork {
[INFO] [stdout]     fn begin_transaction(&self) -> Result<()> {
[INFO] [stdout]         let mut transaction = common::long_operation::lock_or_recover(&self.transaction);
[INFO] [stdout]         *transaction = Some(Transaction::begin_read_transaction(&self.context)?);
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] 
[INFO] [stdout]     fn end_transaction(&self) -> Result<()> {
[INFO] [stdout]         let mut transaction = common::long_operation::lock_or_recover(&self.transaction);
[INFO] [stdout]         transaction.take()
[INFO] [stdout]             .ok_or_else(|| anyhow::anyhow!("No active transaction"))?
[INFO] [stdout]             .end_read_transaction()?;
[INFO] [stdout]         Ok(())
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] //TODO: adapt entities and actions to real use :
[INFO] [stdout] // GetRO, GetMultiRO, GetRelationshipRO, GetRelationshipsFromRightIdsRO
[INFO] [stdout] //
[INFO] [stdout] // You have here a long operation read-only unit of work.
[INFO] [stdout] //
[INFO] [stdout] // RO means Read Only, so *RO actions should be used here.
[INFO] [stdout] // Don't forget to set thread_safe = true for long operation's unit of work.
[INFO] [stdout] // Do not mix read-only and write actions in the same unit of work.
[INFO] [stdout] //
[INFO] [stdout] // Exactly the same macros (without thread_safe) must be set in the use case file in ../use_cases/do_thing_uc.rs
[INFO] [stdout] //
[INFO] [stdout] #[macros::uow_action(entity = "Thing", action = "GetRO", thread_safe = true)]
[INFO] [stdout] #[macros::uow_action(entity = "Thing", action = "GetMultiRO", thread_safe = true)]
[INFO] [stdout] impl DoThingUnitOfWorkTrait for DoThingUnitOfWork {
[INFO] [stdout]     fn publish_do_thing_event(&self, ids: Vec<EntityId>, data: Option<String>) {
[INFO] [stdout]         self.event_hub.send_event(Event {
[INFO] [stdout]             origin: Origin::Doing(DoThing),
[INFO] [stdout]             ids,
[INFO] [stdout]             data,
[INFO] [stdout]         });
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] pub struct DoThingUnitOfWorkFactory {
[INFO] [stdout]     context: DbContext,
[INFO] [stdout]     event_hub: Arc<EventHub>,
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl DoThingUnitOfWorkFactory {
[INFO] [stdout]     pub fn new(db_context: &DbContext, event_hub: &Arc<EventHub>) -> Self {
[INFO] [stdout]         DoThingUnitOfWorkFactory {
[INFO] [stdout]             context: db_context.clone(),
[INFO] [stdout]             event_hub: event_hub.clone(),
[INFO] [stdout]         }
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] impl DoThingUnitOfWorkFactoryTrait for DoThingUnitOfWorkFactory {
[INFO] [stdout]     fn create(&self) -> Box<dyn DoThingUnitOfWorkTrait> {
[INFO] [stdout]         Box::new(DoThingUnitOfWork::new(&self.context, &self.event_hub))
[INFO] [stdout]     }
[INFO] [stdout] }
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] stack backtrace:
[INFO] [stdout]    0:     0x5a49745cc881 - <<std[617be2ca0a6fd877]::sys::backtrace::BacktraceLock>::print::DisplayBacktrace as core[a373ad73a504971]::fmt::Display>::fmt
[INFO] [stdout]    1:     0x5a49745e469a - core[a373ad73a504971]::fmt::write
[INFO] [stdout]    2:     0x5a49745d10dc - <alloc[2e1b2a31b91974c5]::vec::Vec<u8> as core[a373ad73a504971]::io::write::Write>::write_fmt
[INFO] [stdout]    3:     0x5a49745a75d6 - std[617be2ca0a6fd877]::panicking::default_hook::{closure#0}
[INFO] [stdout]    4:     0x5a49745c4669 - std[617be2ca0a6fd877]::panicking::default_hook
[INFO] [stdout]    5:     0x5a497438fc20 - test[aada8e7e76ba119b]::test_main_inner::{closure#0}
[INFO] [stdout]    6:     0x5a49745c4822 - std[617be2ca0a6fd877]::panicking::panic_with_hook
[INFO] [stdout]    7:     0x5a49745a7682 - std[617be2ca0a6fd877]::panicking::panic_handler::{closure#0}
[INFO] [stdout]    8:     0x5a497459fc79 - std[617be2ca0a6fd877]::sys::backtrace::__rust_end_short_backtrace::<std[617be2ca0a6fd877]::panicking::panic_handler::{closure#0}, !>
[INFO] [stdout]    9:     0x5a49745a839d - __rustc[ea4f61d10b84890b]::rust_begin_unwind
[INFO] [stdout]   10:     0x5a49745e4ebc - core[a373ad73a504971]::panicking::panic_fmt
[INFO] [stdout]   11:     0x5a497430eb66 - rust_file_generation[bc330dc3ad16d42f]::use_cases::common::rust_code_generator::rust_code_generator_tests::assert_is_valid_rust
[INFO] [stdout]   12:     0x5a4974321e2c - <rust_file_generation[bc330dc3ad16d42f]::use_cases::common::rust_code_generator::rust_code_generator_tests::feature_use_case_uow_long_operation_read_only_template_has_no_guard_and_renders_valid_rust::{closure#0} as core[a373ad73a504971]::ops::function::FnOnce<()>>::call_once
[INFO] [stdout]   13:     0x5a497438306b - test[aada8e7e76ba119b]::__rust_begin_short_backtrace::<core[a373ad73a504971]::result::Result<(), alloc[2e1b2a31b91974c5]::string::String>, fn() -> core[a373ad73a504971]::result::Result<(), alloc[2e1b2a31b91974c5]::string::String>>
[INFO] [stdout]   14:     0x5a4974390245 - test[aada8e7e76ba119b]::run_test::{closure#0}
[INFO] [stdout]   15:     0x5a4974389b34 - std[617be2ca0a6fd877]::sys::backtrace::__rust_begin_short_backtrace::<test[aada8e7e76ba119b]::run_test::{closure#1}, ()>
[INFO] [stdout]   16:     0x5a49743932d2 - <std[617be2ca0a6fd877]::thread::lifecycle::spawn_unchecked<test[aada8e7e76ba119b]::run_test::{closure#1}, ()>::{closure#1} as core[a373ad73a504971]::ops::function::FnOnce<()>>::call_once::{shim:vtable#0}
[INFO] [stdout]   17:     0x5a49745cba6f - <std[617be2ca0a6fd877]::sys::thread::unix::Thread>::new::thread_start
[INFO] [stdout]   18:     0x78bb6f0cbdfa - <unknown>
[INFO] [stdout]   19:     0x78bb6f15f3d4 - clone
[INFO] [stdout]   20:                0x0 - <unknown>
[INFO] [stdout] 
[INFO] [stdout] 
[INFO] [stdout] failures:
[INFO] [stdout]     use_cases::common::rust_code_generator::rust_code_generator_tests::entity_table_checks_ownership_through_the_junction_index
[INFO] [stdout]     use_cases::common::rust_code_generator::rust_code_generator_tests::entity_units_of_work_template_wires_the_guard_and_renders_valid_rust
[INFO] [stdout]     use_cases::common::rust_code_generator::rust_code_generator_tests::feature_use_case_uow_long_operation_read_only_template_has_no_guard_and_renders_valid_rust
[INFO] [stdout]     use_cases::common::rust_code_generator::rust_code_generator_tests::feature_use_case_uow_long_operation_write_template_wires_the_guard_and_renders_valid_rust
[INFO] [stdout]     use_cases::common::rust_code_generator::rust_code_generator_tests::feature_use_case_uow_read_only_template_has_no_guard_and_renders_valid_rust
[INFO] [stdout]     use_cases::common::rust_code_generator::rust_code_generator_tests::feature_use_case_uow_write_template_wires_the_guard_and_renders_valid_rust
[INFO] [stdout]     use_cases::common::rust_code_generator::rust_code_generator_tests::hashmap_store_indexes_exclusive_junctions_and_validates_without_walking_them
[INFO] [stdout]     use_cases::common::rust_code_generator::rust_code_generator_tests::write_guard_template_renders_valid_rust
[INFO] [stdout] 
[INFO] [stdout] test result: FAILED. 11 passed; 8 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.22s
[INFO] [stdout] 
[INFO] running `Command { std: "docker" "inspect" "c6042921e2f05fa53d5fc40b158d167ba21358e720edd6a71a81d341830935e0", kill_on_drop: false }`
[INFO] running `Command { std: "docker" "rm" "-f" "c6042921e2f05fa53d5fc40b158d167ba21358e720edd6a71a81d341830935e0", kill_on_drop: false }`
[INFO] [stdout] c6042921e2f05fa53d5fc40b158d167ba21358e720edd6a71a81d341830935e0
