Skip to main content

max / alloy

Make the recovery re-run the install rather than only enroll The recovery could repair one thing: the keyslots bootc wipes. Everything else it could only judge. But the failure it exists for can land on any post-deploy stage, and for most of them the repair is simply to do the stage again. So the recovery now re-runs configure_plan against the disk as it stands, and only then asks whether what is there is a machine. That needs the tail to survive a second pass. Audited: systemd-firstboot takes --force, mkdir takes -p, tee truncates, chmod/chown/chpasswd and systemctl enable are idempotent already, and video_group_line returns nothing when the group is present. Two were not: - useradd exits 9 against an existing account, so it is now behind a read of the target's passwd file and runs only when there is no account. - enroll_plan adds a second pair of keyslots on a retry. Left alone deliberately: duplicate slots are harmless, and gating on "a non-TPM slot exists" would skip the phrase when the passphrase enrolled and the phrase did not, which is the one case that must not be skipped. best_effort covers the rest. A repair step's exit status is not the state of the disk, so nothing in the retry can fail the run or resolve it into a failure; the checks decide, by looking. Also fixes a real defect in the previous commit. wipefs opens the device exclusively, so it refuses a mounted filesystem or an open LUKS mapper, and the recovery reaches the wreck having just mounted the target to inspect it. Measured: "probing initialization failed: Device or resource busy". The wreck now unmounts, closes the mapper, and keeps --force as a backstop, so it works in the case it exists for. Choices carries the answers both plans need, since they need the same ones, and finish_plan names the configure-assert-release composition.
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-08-06 17:21 UTC
Signed with PGP, not checked
Commit: 46412f3c49d8a892fc50d78fbb06f4af60087892
Parent: 54ded45
1 file changed, +263 insertions, -94 deletions
@@ -1107,6 +1107,27 @@
1107 1107 /// resolvers, where the partition path finally exists. This one is what the
1108 1108 /// caller hands over, and `Option` is what carries the user's answer: `None` is
1109 1109 /// an unencrypted install, which is why nothing downstream needs a bool.
1110 + /// Everything the work after the deploy needs to know.
1111 + ///
1112 + /// One struct because [`install_plan`] and [`recover_plan`] need exactly the
1113 + /// same answers: the recovery re-runs the install's own configuration, so a
1114 + /// value either of them could do without is a value neither of them has.
1115 + /// Passing them separately meant two argument lists to keep in step, and the
1116 + /// one that drifted would drift silently.
1117 + ///
1118 + /// Not [`Answers`], which is the wizard's record of what was typed and holds
1119 + /// no password: this is the plan's view of it, borrowed for the moment the plan
1120 + /// is built, with the secrets that deliberately live nowhere else. See
1121 + /// [`Secret`].
1122 + struct Choices<'a> {
1123 + hostname: &'a str,
1124 + username: &'a str,
1125 + password: &'a str,
1126 + pubkey: Option<&'a str>,
1127 + locate_timezone: bool,
1128 + encryption: Option<&'a EncryptionChoice>,
1129 + }
1130 +
1110 1131 #[derive(Debug, Clone)]
1111 1132 struct EncryptionChoice {
1112 1133 passphrase: String,
@@ -1192,21 +1213,43 @@
1192 1213 /// Nothing recoverable is destroyed. The target is a disk the user consented to
1193 1214 /// erasing before the install began, and the install had already erased it.
1194 1215 fn wipe_signatures(target: &str) -> Invocation {
1195 - Invocation::new("wipefs").arg("--all").arg(target)
1216 + Invocation::new("wipefs")
1217 + .arg("--all")
1218 + .arg("--force")
1219 + .arg(target)
1196 1220 }
1197 1221
1198 - /// Say what is about to be destroyed, then destroy it.
1222 + /// Say what is about to be destroyed, tear down what holds it, then destroy it.
1199 1223 ///
1200 1224 /// The line matters as much as the command. A user watching the run screen sees
1201 1225 /// their install fail and then sees a disk being wiped, and without the reason
1202 1226 /// between them the second reads as the installer compounding the first.
1203 - fn wreck_plan(target: &str, why: &str) -> Vec<Stage> {
1204 - vec![
1205 - Stage::Run(Invocation::new("echo").arg(format!(
1206 - "{why}. Clearing {target} so this disk cannot be mistaken for an install"
1207 - ))),
1208 - Stage::Run(wipe_signatures(target)),
1209 - ]
1227 + ///
1228 + /// The teardown is not tidiness. `wipefs` opens the device exclusively, so it
1229 + /// refuses a mounted filesystem or a LUKS container whose mapper is open, and
1230 + /// the recovery reaches here having just mounted the target to look at it.
1231 + /// Measured: `wipefs --all` against a container with its mapper open fails with
1232 + /// "probing initialization failed: Device or resource busy". Without this the
1233 + /// wreck would fail in exactly the case it exists for.
1234 + ///
1235 + /// `--force` behind the teardown rather than instead of it, for whatever else
1236 + /// on a live system has picked the device up. This disk is already condemned;
1237 + /// what must not happen is it surviving as something that looks installable.
1238 + fn wreck_plan(partition: &str, device: Option<&str>, why: &str) -> Vec<Stage> {
1239 + let mut stages = vec![Stage::Run(Invocation::new("echo").arg(format!(
1240 + "{why}. Clearing {partition} so this disk cannot be mistaken for an install"
1241 + )))];
1242 +
1243 + // Attempts: on the dead-end path nothing may be mounted or open yet, and
1244 + // "it was not mounted" is not a reason to leave the trap in place.
1245 + stages.push(Stage::Attempt(unmount(TARGET_MOUNT)));
1246 + if let Some(device) = device.filter(|device| *device != partition) {
1247 + stages.push(Stage::Attempt(
1248 + Invocation::new("cryptsetup").arg("close").arg(device),
1249 + ));
1250 + }
1251 + stages.push(Stage::Run(wipe_signatures(partition)));
1252 + stages
1210 1253 }
1211 1254
1212 1255 /// Whether the header in `dump` has a keyslot that is not the TPM's.
@@ -1376,6 +1419,16 @@
1376 1419 .collect()
1377 1420 }
1378 1421
1422 + /// What the wreck would have to clear, carried alongside the checks.
1423 + ///
1424 + /// Two values because clearing a LUKS container means closing the mapper
1425 + /// opened inside it first, and only the device knows that mapper's name.
1426 + #[derive(Clone)]
1427 + struct Wreckable {
1428 + partition: String,
1429 + device: String,
1430 + }
1431 +
1379 1432 /// The same checks as the recovery runs them: a false answer wrecks the disk.
1380 1433 ///
1381 1434 /// Each check decides whether the next one is asked, so the first fact that is
@@ -1386,7 +1439,7 @@
1386 1439 /// because a check's verdict is not knowable until its command has run.
1387 1440 fn verify_or_wreck(
1388 1441 mut checks: std::collections::VecDeque<Check>,
1389 - target: String,
1442 + target: Wreckable,
1390 1443 intact: Arc<AtomicBool>,
1391 1444 ) -> Vec<Stage> {
1392 1445 let Some(Check {
@@ -1401,7 +1454,11 @@
1401 1454 invocation,
1402 1455 then: Box::new(move |output| {
1403 1456 if let Err(message) = verdict(output) {
1404 - return Ok(wreck_plan(&target, &message));
1457 + return Ok(wreck_plan(
1458 + &target.partition,
1459 + Some(&target.device),
1460 + &message,
1461 + ));
1405 1462 }
1406 1463 if checks.is_empty() {
1407 1464 intact.store(true, Ordering::Relaxed);
@@ -1479,6 +1536,32 @@
1479 1536 }
1480 1537 }
1481 1538
1539 + /// The same stages, with nothing in them able to decide the run.
1540 + ///
1541 + /// For the recovery's repair phase. Re-running the configuration against a disk
1542 + /// that is already in an unknown state will step on things: a command may fail
1543 + /// because the work was already done, or because the state it needed is exactly
1544 + /// what went missing. Neither is an answer about the disk, and the checks that
1545 + /// follow are.
1546 + ///
1547 + /// So a [`Stage::Run`] becomes a [`Stage::Attempt`], and a
1548 + /// [`Stage::Resolve`] that cannot make sense of its output contributes nothing
1549 + /// instead of failing the run. The transformation follows the stages a resolver
1550 + /// produces, because those are configuration too.
1551 + fn best_effort(stages: Vec<Stage>) -> Vec<Stage> {
1552 + stages
1553 + .into_iter()
1554 + .map(|stage| match stage {
1555 + Stage::Run(invocation) | Stage::Attempt(invocation) => Stage::Attempt(invocation),
1556 + Stage::Commit(invocation) => Stage::Commit(invocation),
1557 + Stage::Resolve { invocation, then } => Stage::Resolve {
1558 + invocation,
1559 + then: Box::new(move |output| Ok(best_effort(then(output).unwrap_or_default()))),
1560 + },
1561 + })
1562 + .collect()
1563 + }
1564 +
1482 1565 /// What is owed to the disk when the install fails past its commit point.
1483 1566 ///
1484 1567 /// Before the commit point a failure owes nothing: the machine is as it was
@@ -1490,10 +1573,15 @@
1490 1573 /// matter.
1491 1574 ///
1492 1575 /// **Repair forward.** Find the disk again and mount it, through
1493 - /// [`deployed_disk_plan`], the same chain the install used, and then enroll the
1494 - /// keyslots bootc wipes. The repair runs as [`Stage::Attempt`]s: one that fails
1495 - /// must not stop the checks, because what decides this is the state of the disk
1496 - /// and not the exit status of a retry.
1576 + /// [`deployed_disk_plan`], and re-run [`configure_plan`] against it: the whole
1577 + /// of the install's post-deploy work, done a second time. A failure at the last
1578 + /// stage of an otherwise good install is then repaired by redoing that stage,
1579 + /// rather than by condemning the machine over it.
1580 + ///
1581 + /// The retry is [`best_effort`], so nothing in it can decide the outcome. That
1582 + /// is not laxity: a command here can fail because its work was already done, or
1583 + /// because the state it needed is exactly what went missing, and neither is an
1584 + /// answer about the disk.
1497 1585 ///
1498 1586 /// **Ask whether it is a machine.** [`acceptance_checks`], the same four facts
1499 1587 /// the install asserts at its end. Using the install's own definition is the
@@ -1508,52 +1596,67 @@
1508 1596 /// cannot even be shown to hold a deployment, it certainly cannot be shown to
1509 1597 /// hold a machine, and the whole disk is cleared rather than a partition that
1510 1598 /// was never identified.
1511 - fn recover_plan(
1512 - disk: &str,
1513 - username: &str,
1514 - encryption: Option<&EncryptionChoice>,
1515 - intact: &Arc<AtomicBool>,
1516 - ) -> Vec<Stage> {
1517 - let username = username.to_string();
1518 - let choice = encryption.cloned();
1599 + fn recover_plan(disk: &str, choices: &Choices<'_>, intact: &Arc<AtomicBool>) -> Vec<Stage> {
1600 + let hostname = choices.hostname.to_string();
1601 + let username = choices.username.to_string();
1602 + let password = choices.password.to_string();
1603 + let pubkey = choices.pubkey.map(str::to_string);
1604 + let locate_timezone = choices.locate_timezone;
1605 + let panel = crate::display::detect_panel();
1606 + let choice = choices.encryption.cloned();
1519 1607 let intact = Arc::clone(intact);
1520 1608
1521 1609 // Before the root partition is known there is nothing narrower to clear.
1522 1610 let whole_disk = disk.to_string();
1523 - let dead_end: DeadEnd = std::rc::Rc::new(move |message| wreck_plan(&whole_disk, &message));
1611 + let dead_end: DeadEnd =
1612 + std::rc::Rc::new(move |message| wreck_plan(&whole_disk, None, &message));
1524 1613
1525 1614 deployed_disk_plan(
1526 1615 disk,
1527 1616 Some(dead_end),
1528 - Box::new(move |partition, root| {
1529 - let mut stages = Vec::new();
1530 -
1531 - // The one repair that does not depend on anything the checks are
1532 - // about to test, and the one whose absence is irreversible: a
1533 - // volume with no slot but the TPM's cannot be given one later by
1534 - // anybody, where an account can be added from any live medium.
1535 - if let Some(choice) = &choice {
1536 - stages.extend(
1537 - enroll_plan(partition, &choice.passphrase, &choice.recovery)
1538 - .into_iter()
1539 - .map(|stage| match stage {
1540 - Stage::Run(invocation) => Stage::Attempt(invocation),
1541 - other => other,
1542 - }),
1543 - );
1544 - }
1545 -
1617 + Box::new(move |partition, device, root| {
1546 1618 let encryption = choice.as_ref().map(|choice| Encryption {
1547 1619 partition,
1548 1620 passphrase: &choice.passphrase,
1549 1621 recovery: &choice.recovery,
1550 1622 });
1623 +
1624 + // Everything the install does after the deploy, run again against
1625 + // the disk as it now stands. A failure at the last stage of an
1626 + // otherwise good install is repaired by redoing the last stage,
1627 + // rather than by condemning the machine over it.
1628 + //
1629 + // Best-effort throughout: none of it may decide the outcome. The
1630 + // checks below do that, by looking.
1631 + let mut stages = best_effort(
1632 + configure_plan(
1633 + &hostname,
1634 + &username,
1635 + &password,
1636 + &HomeSeeds {
1637 + pubkey: pubkey.as_deref(),
1638 + panel: panel.as_ref(),
1639 + },
1640 + locate_timezone,
1641 + encryption.as_ref(),
1642 + root,
1643 + )
1644 + .unwrap_or_default(),
1645 + );
1646 +
1551 1647 stages.extend(verify_or_wreck(
1552 1648 acceptance_checks(&username, encryption.as_ref(), root).into(),
1553 - partition.to_string(),
1649 + Wreckable {
1650 + partition: partition.to_string(),
1651 + device: device.to_string(),
1652 + },
1554 1653 intact,
1555 1654 ));
1556 1655
1656 + // Tolerated: the wreck unmounts for itself, so on that path this is
1657 + // a second umount of nothing.
1658 + stages.push(Stage::Attempt(unmount(TARGET_MOUNT)));
1659 +
1557 1660 Ok(stages)
1558 1661 }),
1559 1662 )
@@ -1608,6 +1711,10 @@
1608 1711 let group_root = root.to_string();
1609 1712 let group_file = format!("{root}/etc/group");
1610 1713
1714 + let useradd_root = root.to_string();
1715 + let useradd_user = username.to_string();
1716 + let useradd_home = installed_home.clone();
1717 +
1611 1718 let mut stages = vec![
1612 1719 // Before anything is written: a shell the target will not hand out
1613 1720 // makes an account nobody can log into, and useradd will not catch it.
@@ -1692,14 +1799,31 @@
1692 1799 // their install reads it as a failure. Left alone rather than
1693 1800 // papered over: a spool nothing on the image delivers to would be
1694 1801 // invented purely to quiet a warning.
1695 - Stage::Run(
1696 - in_target(root, "useradd")
1697 - .arg("--no-create-home")
1698 - .args(["--home-dir", &installed_home])
1699 - .args(["--shell", LOGIN_SHELL])
1700 - .args(["--groups", "wheel,video"])
1701 - .arg(username),
1702 - ),
1802 + Stage::Resolve {
1803 + invocation: Invocation::new("cat").arg(format!("{useradd_root}/etc/passwd")),
1804 + then: Box::new(move |listing| {
1805 + // useradd exits 9 against an account that already exists, so
1806 + // an unguarded call is a stage that can only run once. The
1807 + // recovery re-runs this whole plan against a disk that may
1808 + // already carry the account, and a stage that fails because
1809 + // its work was already done tells nobody anything.
1810 + //
1811 + // Asked of the target's own passwd file rather than of getent,
1812 + // for the reason the id lookup below states: getent answers
1813 + // about the machine doing the installing.
1814 + if passwd_ids(listing, &useradd_user).is_ok() {
1815 + return Ok(Vec::new());
1816 + }
1817 + Ok(vec![Stage::Run(
1818 + in_target(&useradd_root, "useradd")
1819 + .arg("--no-create-home")
1820 + .args(["--home-dir", &useradd_home])
1821 + .args(["--shell", LOGIN_SHELL])
1822 + .args(["--groups", "wheel,video"])
1823 + .arg(&useradd_user),
1824 + )])
1825 + }),
1826 + },
1703 1827 // -p because /var/home does not exist yet either.
1704 1828 Stage::Run(Invocation::new("mkdir").args(["-p", &home])),
1705 1829 // The dotfiles --create-home would have copied. Without them an
@@ -1833,29 +1957,6 @@
1833 1957 ));
1834 1958 }
1835 1959
1836 - stages.extend(acceptance_plan(username, encryption, root));
1837 -
1838 - stages.extend([
1839 - // Upstream: "optional, but recommended to run as the penultimate step
1840 - // before unmounting the target filesystem. This command will perform
1841 - // some basic sanity checks and may also perform fixups on the target
1842 - // root." `--help` confirms the shape: `finalize <ROOT_PATH>`, the
1843 - // mounted root filesystem, which is what is passed.
1844 - //
1845 - // It bills itself as a step of an `install to-filesystem` flow, and
1846 - // `to-disk` already finalizes and unmounts by itself. Against a real
1847 - // to-disk install this exits 0 without output, so it is kept as a
1848 - // cheap sanity check rather than for any fixup it is known to make.
1849 - Stage::Run(
1850 - Invocation::new("bootc")
1851 - .args(["install", "finalize"])
1852 - .arg(TARGET_MOUNT),
1853 - ),
1854 - // Leaving the target mounted would strand the filesystem dirty across
1855 - // the reboot the user is about to perform.
1856 - Stage::Run(Invocation::new("umount").arg(TARGET_MOUNT)),
1857 - ]);
1858 -
1859 1960 Ok(stages)
1860 1961 }
1861 1962
@@ -2055,10 +2156,16 @@
2055 2156
2056 2157 /// What to do once the disk has been found to hold a deployment.
2057 2158 ///
2058 - /// Takes the root partition and the deployment directory, the two values that
2059 - /// cannot be known before the deploy has run. The install configures; the
2060 - /// recovery checks.
2061 - type Deployed = Box<dyn FnOnce(&str, &str) -> Result<Vec<Stage>, String>>;
2159 + /// Takes the root partition, the device the filesystem is actually on, and the
2160 + /// deployment directory: the values that cannot be known before the deploy has
2161 + /// run. The install configures; the recovery checks.
2162 + ///
2163 + /// The partition and the device differ exactly when the volume is encrypted,
2164 + /// where the partition is the LUKS container and the device is the mapper
2165 + /// opened inside it. Both are needed: the header being enrolled into is on the
2166 + /// partition, and the mapper is what has to be closed before the partition can
2167 + /// be cleared.
2168 + type Deployed = Box<dyn FnOnce(&str, &str, &str) -> Result<Vec<Stage>, String>>;
2062 2169
2063 2170 /// What to do when the chain cannot go on.
2064 2171 ///
@@ -2132,6 +2239,7 @@
2132 2239 return or_dead_end(Err(message), contents_end.as_ref());
2133 2240 }
2134 2241 };
2242 + let opened = device.clone();
2135 2243 Ok(vec![
2136 2244 // Release whatever bootc left mounted on the device
2137 2245 // before mounting it. Its own leftover is read-only,
@@ -2160,6 +2268,7 @@
2160 2268 Stage::Resolve {
2161 2269 invocation: mount_options(TARGET_MOUNT),
2162 2270 then: Box::new(move |options| {
2271 + let device = opened;
2163 2272 if let Err(message) = writable_mount(options) {
2164 2273 return or_dead_end(Err(message), mount_end.as_ref());
2165 2274 }
@@ -2178,7 +2287,7 @@
2178 2287 );
2179 2288 }
2180 2289 or_dead_end(
2181 - then(&partition, deployment),
2290 + then(&partition, &device, deployment),
2182 2291 deployment_end.as_ref(),
2183 2292 )
2184 2293 }),
@@ -2229,19 +2338,13 @@
2229 2338 /// This is the discipline the mount check already stated for itself, applied to
2230 2339 /// the stage where being wrong is most expensive. `mount` warns and exits 0 when
2231 2340 /// it falls back to read-only, so its success was never evidence either.
2232 - fn install_plan(
2233 - disk: &str,
2234 - hostname: &str,
2235 - username: &str,
2236 - password: &str,
2237 - pubkey: Option<&str>,
2238 - locate_timezone: bool,
2239 - encryption: Option<EncryptionChoice>,
2240 - ) -> Vec<Stage> {
2241 - let hostname = hostname.to_string();
2242 - let username = username.to_string();
2243 - let password = password.to_string();
2244 - let pubkey = pubkey.map(str::to_string);
2341 + fn install_plan(disk: &str, choices: &Choices<'_>) -> Vec<Stage> {
2342 + let hostname = choices.hostname.to_string();
2343 + let username = choices.username.to_string();
2344 + let password = choices.password.to_string();
2345 + let pubkey = choices.pubkey.map(str::to_string);
2346 + let locate_timezone = choices.locate_timezone;
2347 + let encryption = choices.encryption.cloned();
2245 2348 let encrypt = encryption.is_some();
2246 2349 // Read here, once, on the machine being installed. The panel does not
2247 2350 // change while the install runs, and reading it before the disk is touched
@@ -2307,7 +2410,7 @@
2307 2410 stages.extend(deployed_disk_plan(
2308 2411 disk,
2309 2412 None,
2310 - Box::new(move |partition, deployment| {
2413 + Box::new(move |partition, _device, deployment| {
2311 2414 // The container, not the mapper device: the header being enrolled
2312 2415 // into is on the partition.
2313 2416 let encryption = encryption.as_ref().map(|choice| Encryption {
@@ -2315,7 +2418,7 @@
2315 2418 passphrase: &choice.passphrase,
2316 2419 recovery: &choice.recovery,
2317 2420 });
2318 - configure_plan(
2421 + finish_plan(
2319 2422 &hostname,
2320 2423 &username,
2321 2424 &password,
@@ -2332,6 +2435,61 @@
2332 2435 stages
2333 2436 }
2334 2437
2438 + /// Configure the deployment, assert it is a machine, and release it.
2439 + ///
2440 + /// The whole of the install's post-deploy half, in the order the three parts
2441 + /// have to happen: nothing can be checked before it is configured, and nothing
2442 + /// can be checked after the mount it is read through is gone.
2443 + #[allow(clippy::too_many_arguments)]
2444 + fn finish_plan(
2445 + hostname: &str,
2446 + username: &str,
2447 + password: &str,
2448 + seeds: &HomeSeeds<'_>,
2449 + locate_timezone: bool,
2450 + encryption: Option<&Encryption<'_>>,
2451 + deployment: &str,
2452 + ) -> Result<Vec<Stage>, String> {
2453 + let mut stages = configure_plan(
2454 + hostname,
2455 + username,
2456 + password,
2457 + seeds,
2458 + locate_timezone,
2459 + encryption,
2460 + deployment,
2461 + )?;
2462 +
2463 + // Asserted before the target is finalized and released, because every
2464 + // check reads it through the mount.
2465 + stages.extend(acceptance_plan(username, encryption, deployment));
2466 +
2467 + stages.extend([
2468 + // Upstream: "optional, but recommended to run as the
2469 + // penultimate step before unmounting the target filesystem.
2470 + // This command will perform some basic sanity checks and may
2471 + // also perform fixups on the target root." `--help` confirms
2472 + // the shape: `finalize <ROOT_PATH>`, the mounted root
2473 + // filesystem, which is what is passed.
2474 + //
2475 + // It bills itself as a step of an `install to-filesystem`
2476 + // flow, and `to-disk` already finalizes and unmounts by
2477 + // itself. Against a real to-disk install this exits 0 without
2478 + // output, so it is kept as a cheap sanity check rather than
2479 + // for any fixup it is known to make.
2480 + Stage::Run(
2481 + Invocation::new("bootc")
2482 + .args(["install", "finalize"])
2483 + .arg(TARGET_MOUNT),
2484 + ),
2485 + // Leaving the target mounted would strand the filesystem dirty
2486 + // across the reboot the user is about to perform.
2487 + Stage::Run(unmount(TARGET_MOUNT)),
2488 + ]);
2489 +
2490 + Ok(stages)
2491 + }
2492 +
2335 2493 // ---- disks ----
2336 2494
2337 2495 /// A whole disk, as an install target.
@@ -3724,18 +3882,36 @@
3724 3882 return Vec::new();
3725 3883 };
3726 3884
3885 + let encryption = self.answers.encrypt.then(|| EncryptionChoice {
3886 + passphrase: passphrase.to_string(),
3887 + recovery: recovery.to_string(),
3888 + });
3889 +
3727 3890 install_plan(
3728 3891 disk,
3892 + &self.choices(hostname, username, password, encryption.as_ref()),
3893 + )
3894 + }
3895 +
3896 + /// The answers the plans take, gathered from the view.
3897 + ///
Lines truncated